GIT 28652c22864065b364311b544a3c8903872688e5 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6.git#test commit 3ede41c718c7845905231019e42d05a3ed329515 Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:34:00 2007 -0300 ACPI: ibm-acpi: move driver to drivers/misc hierarchy ibm-acpi is not an ACPICA driver, so move it to drivers/misc as per Len Brown's request. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 38f996ed21089fa4ae40526a5f428e3c792ea561 Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:59 2007 -0300 ACPI: ibm-acpi: update documentation Update documentation header, and relocate a hunk of text that was missplaced. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit a62bc916cf48caaf9efa2fed20440fd617647c6c Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:58 2007 -0300 ACPI: ibm-acpi: update copyright notice Update copyright and license info on the source code comments. No functional changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 56b6aeb05890f219895197f5166637b3d7a6f679 Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:57 2007 -0300 ACPI: ibm-acpi: organize code Shuffle code around to better organize the driver code inside the ibm-acpi.c file. This patch adds no functional changes. It is pure fluff that will make me a bit more productive. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 1406cdd1760743106278c1f02a0f445159c8f400 Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:56 2007 -0300 ACPI: ibm-acpi: add header file Add a (private) header file for ibm-acpi, and move type definitions and ThinkPad driver constants to the new header file. This patch has no functional changes. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit e062e0343871a41e8ec408f1c1e8ac3b0310da9d Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:55 2007 -0300 ACPI: ibm-acpi: rename some identifiers Rename some identifiers so that they are more in tune with the rest of the driver code, or less generic. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 837ca6ddb440c186eaa8e01b69486581d3457f2c Author: Henrique de Moraes Holschuh Date: Fri Mar 23 17:33:54 2007 -0300 ACPI: ibm-acpi: kill trailing whitespace I shall protect the ibm-acpi city against the invasion of the barbarian blanks! To the unforgiving jaws of sed s/[[:blank:]]\+$// they go! Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 439a888885c584f7ac8536a43be80475f9eaed71 Author: Len Brown Date: Thu Mar 22 01:21:05 2007 -0400 ACPI: sbs: remove i2c_ec.[ch] Signed-off-by: Len Brown commit 722062334b972c31a3b83dbf7e9b5a58bb2707dd Author: Vladimir Lebedev Date: Mon Mar 19 17:45:50 2007 +0300 ACPI: sbs: Common interface with CM battery The SBS driver has tne features as CM battery: SBS update_time variable has tne same definition as CM battery 'update_time' variable. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit 6845118b3b7a9cc2ba14dc665370217bc3ba8057 Author: Vladimir Lebedev Date: Mon Mar 19 17:45:50 2007 +0300 ACPI: sbs: Debug messages correction/improvement Debug messages correction/improvement: Use ACPI_EXCEPTION instead of ACPI_DEBUG_PRINT. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit 84cb55987236ffea062a35fbe1441768b6bb2722 Author: Vladimir Lebedev Date: Mon Mar 19 17:45:50 2007 +0300 ACPI: sbs: remove I2C Makefile hooks SBS does not depend on I2C. i2c_ec.h and i2c_ec.c are not needed Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit b4150fc4ae20621edf2f8e1ea5ce13eb2c803e7a Author: Vladimir Lebedev Date: Mon Mar 19 17:45:50 2007 +0300 ACPI: sbs: remove I2C Kconfig dependency SBS does not depend on I2C. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit 6d15702cc07503b74494dc4f1ddb15f354987b14 Author: Vladimir Lebedev Date: Mon Mar 19 17:45:50 2007 +0300 ACPI: sbs: use EC rather than I2C SBS is based on EC function(ec_read/ec_write). Not needed using of I2C structures/functions ... is removed. SBS does not depend on I2C now. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit 8ec555c2c4c6c33759a1dbb13fa8f3b14fc77e10 Author: Corentin Chary Date: Sun Mar 11 10:28:03 2007 +0100 asus-laptop: version bump Version and copyright bump. Signed-off-by: Corentin Chary Signed-off-by: Len Brown commit 185e5af98b1e09b1e0f859332243223776b2ad57 Author: Corentin Chary Date: Sun Mar 11 10:27:33 2007 +0100 asus-laptop: add wapf param Add the "wapf" param. This param allows to define the behavior of the Fn F2 key (wlan switch). Signed-off-by: Corentin Chary Signed-off-by: Len Brown commit fdd8d08084663242b42e27f7d71739f3f9009286 Author: Corentin Chary Date: Sun Mar 11 10:26:48 2007 +0100 asus-laptop: add GLED Add support for "gaming" led. Signed-off-by: Corentin Chary Signed-off-by: Len Brown commit 935ffeeca84fe5d48d0cc9f35c58db42b384229f Author: Corentin Chary Date: Sun Mar 11 10:26:12 2007 +0100 asus-laptop: clean write_status Clean the write_status function, and implement special case with a switch inside write_status. It also make sure bt and wl status are right when booting with the hardware switch off. Signed-off-by: Corentin Chary Signed-off-by: Len Brown commit 9a8168500674b1062afe438d34d0c8216d38dc31 Author: Corentin Chary Date: Sun Mar 11 10:25:38 2007 +0100 asus-laptop: use acpi_evaluate_integer instead of read_acpi_int Use acpi_evaluate_integer() instead of read_acpi_int() Signed-off-by: Corentin Chary Signed-off-by: Len Brown commit 9fd9f8e8bdcfc9aa309dae5bccc55d02804337d0 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Block queries until EC is fully initialized Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 01f2246269639f6aa93086719a8dbec26cb68e98 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Cleanup of EC initialization Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit d033879c9838b960014e861d0eb3bdf11d3b9d9d Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: first_ec is better to be acpi_ec than acpi_device. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit d66d969df88c742494736ed06eeaf3229d3a7259 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Rename ec_ecdt to more informative boot_ec Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit c0900c3512dc8fd0b37f8fbcebc7853ed9efff10 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Clean ECDT and namespace parsing. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit e8284321048aac7be307b3ec5e0631f5c514935a Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Put install handlers into separate function. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 3d02b90be2c7bc7ffbc5e502a135c13838d23ef4 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Remove casts to/from void* from ec.c Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit c45aac43fec2d6ca8d0be8408f94e8176c8110ef Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: enable burst functionality in EC. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 33c7a0738c5f753a7d94fd3b2ec7d84e79a141a8 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: "Fake ECDT" workaround is not needed any longer. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 33d20b6100d05a0b14883e7dc8ab41e4531fcf59 Author: Alexey Starikovskiy Date: Wed Mar 7 22:28:00 2007 +0300 ACPI: EC: Make EC to initialize first in ACPI Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit a5f8dee2d367e69fd57f5ea107072bb72eb15327 Author: Alexey Starikovskiy Date: Wed Mar 7 22:27:59 2007 +0300 ACPI: EC: Don't use Global Lock if not asked to do so Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit ac122bb64b0d51f0512185d3522a75f3f3a80bc9 Author: Ilya A. Volynets-Evenbakh Date: Mon Feb 19 15:19:31 2007 -0800 ACPI: dock: add access to ACPI docking station UID It is useful to know whether your laptop is docked or not, but it is even more useful to know which docking station it's docked to. Attached patch adds "uid" file to sysfs. Tested on Dell Latitude D600 with D/Dock. Patch is against official 2.6.20 release. Signed-off-by: Len Brown commit b51f201f9a9af932c723492551f9e688d34cc468 Author: Len Brown Date: Wed Mar 7 04:37:53 2007 -0500 cpuidle: ladder does not depend on ACPI build fix for CONFIG_ACPI=n In file included from drivers/cpuidle/governors/ladder.c:21: include/acpi/processor.h:88: error: expected specifier-qualifier-list before ‘acpi_integer’ include/acpi/processor.h:106: error: expected specifier-qualifier-list before ‘acpi_integer’ include/acpi/processor.h:168: error: expected specifier-qualifier-list before ‘acpi_handle’ Signed-off-by: Len Brown commit 3d869f55bda8b0b6f0cb55f9a85a6f855a016092 Author: Adrian Bunk Date: Tue Mar 6 02:29:40 2007 -0800 cpuidle: make code static This patch makes the following needlessly global code static: - driver.c: __cpuidle_find_driver() - governor.c: __cpuidle_find_governor() - ladder.c: struct ladder_governor Signed-off-by: Adrian Bunk Cc: Venkatesh Pallipadi Cc: Adam Belay Cc: Shaohua Li Signed-off-by: Andrew Morton Signed-off-by: Len Brown commit bc17374e69a621c9b7f6f9624c9248f3b8e8503d Author: Venkatesh Pallipadi Date: Wed Mar 7 02:38:22 2007 -0500 cpu_idle: fix build break This patch fixes a build breakage with !CONFIG_HOTPLUG_CPU and CONFIG_CPU_IDLE. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Adrian Bunk Signed-off-by: Andrew Morton Signed-off-by: Len Brown commit d98b2065c5ac31a35cbdfe9b47d421352f96fc3f Author: Venkatesh Pallipadi Date: Tue Mar 6 02:29:39 2007 -0800 cpuidle: build fix for !CPU_IDLE Fix the compile issues when CPU_IDLE is not configured. Signed-off-by: Venkatesh Pallipadi Cc: Adam Belay Cc: Shaohua Li Signed-off-by: Andrew Morton Signed-off-by: Len Brown commit 9ea7d57576f40c6af03c8c9fa7a069f2222b498b Author: Vladimir Lebedev Date: Tue Feb 20 15:48:06 2007 +0300 ACPI: battery: Lindent Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit b6ce4083ed8e2a01a3a59301eabe0fc1e68a8a84 Author: Vladimir Lebedev Date: Tue Feb 20 15:48:06 2007 +0300 ACPI: Cache battery status instead of re-evaluating AML /proc exports _BST in a single file, and _BST is re-evaulated whenever that file is read. Sometimes user-space reads this file frequently, and on some systems _BST takes a long time to evaluate due to a slow EC. Further, when we move to sysfs, the values returned from _BST will be in multiple files, and evaluating _BST for each file read would make matters worse. Here code is added to support caching the results of _BST. A new module parameter "update_time" tells how many seconds the cached _BST should be used before it is re-evaluated. Currently, update_time defaults to 0, and so the existing behaviour of re-evaluating on each read retained. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit a1f0eff21edac1bd87e397f56c4258b9611b5a50 Author: Vladimir Lebedev Date: Tue Feb 20 15:48:06 2007 +0300 ACPI: battery: make internal names consistent with battery "state" Cleanup -- No functional changes. Battery state is currently exported in a proc "state" file. Update associated #defines and routines to be consistent. Signed-off-by: Vladimir Lebedev Signed-off-by: Len Brown commit c5bb38e598e68202e0d6f08b3fe0f30f12999357 Author: Venkatesh Pallipadi Date: Thu Feb 22 13:54:57 2007 -0800 cpuidle take2: Basic documentation for cpuidle Documentation for cpuidle infrastructure Signed-off-by: Venkatesh Pallipadi Signed-off-by: Adam Belay Signed-off-by: Shaohua Li Signed-off-by: Len Brown commit 92648781fb5f44cee584da36eb569e88dddc4b8f Author: Venkatesh Pallipadi Date: Thu Feb 22 13:54:03 2007 -0800 cpuidle take2: Hookup ACPI C-states driver with cpuidle Hookup ACPI C-states onto generic cpuidle infrastructure. drivers/acpi/procesor_idle.c is now a ACPI C-states driver that registers as a driver in cpuidle infrastructure and the policy part is removed from drivers/acpi/processor_idle.c. We use governor in cpuidle instead. Signed-off-by: Shaohua Li Signed-off-by: Venkatesh Pallipadi Signed-off-by: Adam Belay Signed-off-by: Len Brown commit 941b1971a405f95537bc6f9a738e737eb2186e45 Author: Venkatesh Pallipadi Date: Thu Feb 22 13:52:57 2007 -0800 cpuidle take2: Core cpuidle infrastructure Announcing 'cpuidle', a new CPU power management infrastructure to manage idle CPUs in a clean and efficient manner. cpuidle separates out the drivers that can provide support for multiple types of idle states and policy governors that decide on what idle state to use at run time. A cpuidle driver can support multiple idle states based on parameters like varying power consumption, wakeup latency, etc (ACPI C-states for example). A cpuidle governor can be usage model specific (laptop, server, laptop on battery etc). Main advantage of the infrastructure being, it allows independent development of drivers and governors and allows for better CPU power management. A huge thanks to Adam Belay and Shaohua Li who were part of this mini-project since its beginning and are greatly responsible for this patchset. This patch: Core cpuidle infrastructure. Introduces a new abstraction layer for cpuidle: * which manages drivers that can support multiple idles states. Drivers can be generic or particular to specific hardware/platform * allows pluging in multiple policy governors that can take idle state policy decision * The core also has a set of sysfs interfaces with which administrato can know about supported drivers and governors and switch them at run time. Signed-off-by: Adam Belay Signed-off-by: Shaohua Li Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown Documentation/cpuidle/core.txt | 17 Documentation/cpuidle/driver.txt | 24 Documentation/cpuidle/governor.txt | 24 Documentation/cpuidle/sysfs.txt | 27 Documentation/ibm-acpi.txt | 91 - arch/i386/Kconfig | 2 arch/x86_64/Kconfig | 2 drivers/Makefile | 1 drivers/acpi/Kconfig | 40 - drivers/acpi/Makefile | 10 drivers/acpi/battery.c | 641 ++++++-- drivers/acpi/dock.c | 24 drivers/acpi/ec.c | 495 ++---- drivers/acpi/i2c_ec.c | 403 ----- drivers/acpi/i2c_ec.h | 23 drivers/acpi/ibm_acpi.c | 2798 ------------------------------------ drivers/acpi/processor_core.c | 3 drivers/acpi/processor_idle.c | 873 ++++------- drivers/acpi/sbs.c | 1291 +++++++++-------- drivers/cpuidle/Kconfig | 28 drivers/cpuidle/Makefile | 5 drivers/cpuidle/cpuidle.c | 283 ++++ drivers/cpuidle/cpuidle.h | 50 + drivers/cpuidle/driver.c | 219 +++ drivers/cpuidle/governor.c | 160 ++ drivers/cpuidle/governors/Makefile | 5 drivers/cpuidle/governors/ladder.c | 227 +++ drivers/cpuidle/sysfs.c | 340 ++++ drivers/misc/Kconfig | 37 drivers/misc/Makefile | 1 drivers/misc/asus-laptop.c | 151 +- drivers/misc/ibm_acpi.c | 2783 ++++++++++++++++++++++++++++++++++++ drivers/misc/ibm_acpi.h | 437 ++++++ include/acpi/processor.h | 2 include/linux/cpuidle.h | 183 ++ 35 files changed, 6667 insertions(+), 5033 deletions(-) diff --git a/Documentation/cpuidle/core.txt b/Documentation/cpuidle/core.txt new file mode 100644 index 0000000..e686cfc --- /dev/null +++ b/Documentation/cpuidle/core.txt @@ -0,0 +1,17 @@ + + Supporting multiple CPU idle levels in kernel + + cpuidle + +General Information: + +Various CPUs today support multiple idle levels that are differentiated +by varying exit latencies and power consumption during idle. +cpuidle is a generic in-kernel infrastructure that separates +idle policy (governor) from idle mechanism (driver) and provides a +standardized infrastructure to support independent development of +governors and drivers. + +cpuidle resides under /drivers/cpuidle. + + diff --git a/Documentation/cpuidle/driver.txt b/Documentation/cpuidle/driver.txt new file mode 100644 index 0000000..2dbee4b --- /dev/null +++ b/Documentation/cpuidle/driver.txt @@ -0,0 +1,24 @@ + + + Supporting multiple CPU idle levels in kernel + + cpuidle drivers + + + + +cpuidle driver supports capability detection for a particular system. The +init and exit routines will be called for each online CPU, with a percpu +cpuidle_driver object and driver should fill in cpuidle_states inside +cpuidle_driver depending on the CPU capability. + +Driver can handle dynamic state changes (like battery<->AC), by calling +force_redetect interface. + +It is possible to have more than one driver registered at the same time and +user can switch between drivers using /sysfs interface. + +Interfaces: +int cpuidle_register_driver(struct cpuidle_driver *drv); +void cpuidle_unregister_driver(struct cpuidle_driver *drv); +int cpuidle_force_redetect(struct cpuidle_device *dev); diff --git a/Documentation/cpuidle/governor.txt b/Documentation/cpuidle/governor.txt new file mode 100644 index 0000000..a0fc3e5 --- /dev/null +++ b/Documentation/cpuidle/governor.txt @@ -0,0 +1,24 @@ + + + + Supporting multiple CPU idle levels in kernel + + cpuidle governors + + + + +cpuidle governor is policy routine that decides what idle state to enter at +any given time. cpuidle core uses different callbacks to governor while +handling idle entry. +* select_state callback where governor can determine next idle state to enter +* prepare_idle callback is called before entering an idle state +* scan callback is called after a driver forces redetection of the states + +More than one governor can be registered at the same time and +user can switch between drivers using /sysfs interface. + +Interfaces: +int cpuidle_register_governor(struct cpuidle_governor *gov); +void cpuidle_unregister_governor(struct cpuidle_governor *gov); + diff --git a/Documentation/cpuidle/sysfs.txt b/Documentation/cpuidle/sysfs.txt new file mode 100644 index 0000000..7fbf644 --- /dev/null +++ b/Documentation/cpuidle/sysfs.txt @@ -0,0 +1,27 @@ + + + Supporting multiple CPU idle levels in kernel + + cpuidle sysfs + +System global cpuidle information are under +/sys/devices/system/cpu/cpuidle + +The current interfaces in this directory has self-explanatory names: +* available_drivers +* available_governors +* current_driver +* current_governor + +Per logical CPU specific cpuidle information are under +/sys/devices/system/cpu/cpuX/cpuidle +for each online cpu X + +Under this percpu directory, there is a directory for each idle state supported +by the driver, which in turn has +* latency +* power +* time +* usage + + diff --git a/Documentation/ibm-acpi.txt b/Documentation/ibm-acpi.txt index 0132d36..f409f4b 100644 --- a/Documentation/ibm-acpi.txt +++ b/Documentation/ibm-acpi.txt @@ -1,16 +1,17 @@ IBM ThinkPad ACPI Extras Driver - Version 0.12 - 17 August 2005 + Version 0.13 + 31 December 2006 Borislav Deianov + Henrique de Moraes Holschuh http://ibm-acpi.sf.net/ This is a Linux ACPI driver for the IBM ThinkPad laptops. It supports various features of these laptops which are accessible through the -ACPI framework but not otherwise supported by the generic Linux ACPI -drivers. +ACPI framework but not otherwise fully supported by the generic Linux +ACPI drivers. Status @@ -21,7 +22,7 @@ detailed description): - Fn key combinations - Bluetooth enable and disable - - video output switching, expansion control + - video output switching, expansion control - ThinkLight on and off - limited docking and undocking - UltraBay eject @@ -472,7 +473,7 @@ This feature dumps the values of 256 emb registers. Values which have changed since the last time the registers were dumped are marked with a star: -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 @@ -503,7 +504,7 @@ vary. The second ensures that the fan-re the fan speed fluctuates a bit. The third will (hopefully) mark the fan register with a star: -[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump +[root@x40 ibm-acpi]# cat /proc/acpi/ibm/ecdump EC +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +0a +0b +0c +0d +0e +0f EC 0x00: a7 47 87 01 fe 96 00 08 01 00 cb 00 00 00 40 00 EC 0x10: 00 00 ff ff f4 3c 87 09 01 ff 42 01 ff ff 0d 00 @@ -638,6 +639,26 @@ The ThinkPad's ACPI DSDT code will repro certain conditions are met. It will override any fan programming done through ibm-acpi. +The ibm-acpi kernel driver can be programmed to revert the fan level +to a safe setting if userspace does not issue one of the fan commands: +"enable", "disable", "level" or "watchdog" within a configurable +ammount of time. To do this, use the "watchdog" command. + + echo 'watchdog ' > /proc/acpi/ibm/fan + +Interval is the ammount of time in seconds to wait for one of the +above mentioned fan commands before reseting the fan level to a safe +one. If set to zero, the watchdog is disabled (default). When the +watchdog timer runs out, it does the exact equivalent of the "enable" +fan command. + +Note that the watchdog timer stops after it enables the fan. It will +be rearmed again automatically (using the same interval) when one of +the above mentioned fan commands is received. The fan watchdog is, +therefore, not suitable to protect against fan mode changes made +through means other than the "enable", "disable", and "level" fan +commands. + EXPERIMENTAL: WAN -- /proc/acpi/ibm/wan --------------------------------------- @@ -670,59 +691,3 @@ example: modprobe ibm_acpi hotkey=enable,0xffff video=auto_disable -The ibm-acpi kernel driver can be programmed to revert the fan level -to a safe setting if userspace does not issue one of the fan commands: -"enable", "disable", "level" or "watchdog" within a configurable -ammount of time. To do this, use the "watchdog" command. - - echo 'watchdog ' > /proc/acpi/ibm/fan - -Interval is the ammount of time in seconds to wait for one of the -above mentioned fan commands before reseting the fan level to a safe -one. If set to zero, the watchdog is disabled (default). When the -watchdog timer runs out, it does the exact equivalent of the "enable" -fan command. - -Note that the watchdog timer stops after it enables the fan. It will -be rearmed again automatically (using the same interval) when one of -the above mentioned fan commands is received. The fan watchdog is, -therefore, not suitable to protect against fan mode changes made -through means other than the "enable", "disable", and "level" fan -commands. - - -Example Configuration ---------------------- - -The ACPI support in the kernel is intended to be used in conjunction -with a user-space daemon, acpid. The configuration files for this -daemon control what actions are taken in response to various ACPI -events. An example set of configuration files are included in the -config/ directory of the tarball package available on the web -site. Note that these are provided for illustration purposes only and -may need to be adapted to your particular setup. - -The following utility scripts are used by the example action -scripts (included with ibm-acpi for completeness): - - /usr/local/sbin/idectl -- from the hdparm source distribution, - see http://www.ibiblio.org/pub/Linux/system/hardware - /usr/local/sbin/laptop_mode -- from the Linux kernel source - distribution, see Documentation/laptop-mode.txt - /sbin/service -- comes with Redhat/Fedora distributions - /usr/sbin/hibernate -- from the Software Suspend 2 distribution, - see http://softwaresuspend.berlios.de/ - -Toan T Nguyen notes that Suse uses the -powersave program to suspend ('powersave --suspend-to-ram') or -hibernate ('powersave --suspend-to-disk'). This means that the -hibernate script is not needed on that distribution. - -Henrik Brix Andersen has written a Gentoo ACPI event -handler script for the X31. You can get the latest version from -http://dev.gentoo.org/~brix/files/x31.sh - -David Schweikert has written an alternative blank.sh -script which works on Debian systems. This scripts has now been -extended to also work on Fedora systems and included as the default -blank.sh in the distribution. diff --git a/arch/i386/Kconfig b/arch/i386/Kconfig index 53d6237..2984341 100644 --- a/arch/i386/Kconfig +++ b/arch/i386/Kconfig @@ -1065,6 +1065,8 @@ endmenu source "arch/i386/kernel/cpu/cpufreq/Kconfig" +source "drivers/cpuidle/Kconfig" + endmenu menu "Bus options (PCI, PCMCIA, EISA, MCA, ISA)" diff --git a/arch/x86_64/Kconfig b/arch/x86_64/Kconfig index 56eb14c..fd01428 100644 --- a/arch/x86_64/Kconfig +++ b/arch/x86_64/Kconfig @@ -670,6 +670,8 @@ source "drivers/acpi/Kconfig" source "arch/x86_64/kernel/cpufreq/Kconfig" +source "drivers/cpuidle/Kconfig" + endmenu menu "Bus options (PCI etc.)" diff --git a/drivers/Makefile b/drivers/Makefile index 3a718f5..7acd123 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_EDAC) += edac/ obj-$(CONFIG_MCA) += mca/ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ +obj-$(CONFIG_CPU_IDLE) += cpuidle/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_NEW_LEDS) += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index e2ce4a9..1ba48be 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -218,43 +218,6 @@ config ACPI_ASUS NOTE: This driver is deprecated and will probably be removed soon, use asus-laptop instead. -config ACPI_IBM - tristate "IBM ThinkPad Laptop Extras" - depends on X86 - select BACKLIGHT_CLASS_DEVICE - ---help--- - This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds - support for Fn-Fx key combinations, Bluetooth control, video - output switching, ThinkLight control, UltraBay eject and more. - For more information about this driver see - and . - - If you have an IBM ThinkPad laptop, say Y or M here. - -config ACPI_IBM_DOCK - bool "Legacy Docking Station Support" - depends on ACPI_IBM - depends on ACPI_DOCK=n - default n - ---help--- - Allows the ibm_acpi driver to handle docking station events. - This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. It will - allow locking and removing the laptop from the docking station, - but will not properly connect PCI devices. - - If you are not sure, say N here. - -config ACPI_IBM_BAY - bool "Legacy Removable Bay Support" - depends on ACPI_IBM - default y - ---help--- - Allows the ibm_acpi driver to handle removable bays. It will allow - disabling the device in the bay, and also generate notifications when - the bay lever is ejected or inserted. - - If you are not sure, say Y here. - config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" depends on X86 @@ -388,11 +351,10 @@ config ACPI_HOTPLUG_MEMORY config ACPI_SBS tristate "Smart Battery System (EXPERIMENTAL)" - depends on X86 && I2C + depends on X86 depends on EXPERIMENTAL help This driver adds support for the Smart Battery System. - Depends on I2C (Device Drivers ---> I2C support) A "Smart Battery" is quite old and quite rare compared to today's ACPI "Control Method" battery. diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 5956e9f..d4336f1 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -1,6 +1,6 @@ # # Makefile for the Linux ACPI interpreter -# +# export ACPI_CFLAGS @@ -32,16 +32,17 @@ # processor-objs += processor_core.o processor_throttling.o \ processor_idle.o processor_thermal.o ifdef CONFIG_CPU_FREQ -processor-objs += processor_perflib.o +processor-objs += processor_perflib.o endif obj-y += sleep/ obj-y += bus.o glue.o obj-y += scan.o +# Keep EC driver first. Initialization of others depend on it. +obj-$(CONFIG_ACPI_EC) += ec.o obj-$(CONFIG_ACPI_AC) += ac.o obj-$(CONFIG_ACPI_BATTERY) += battery.o obj-$(CONFIG_ACPI_BUTTON) += button.o -obj-$(CONFIG_ACPI_EC) += ec.o obj-$(CONFIG_ACPI_FAN) += fan.o obj-$(CONFIG_ACPI_DOCK) += dock.o obj-$(CONFIG_ACPI_BAY) += bay.o @@ -55,8 +56,7 @@ obj-$(CONFIG_ACPI_SYSTEM) += system.o ev obj-$(CONFIG_ACPI_DEBUG) += debug.o obj-$(CONFIG_ACPI_NUMA) += numa.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o -obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o obj-y += cm_sbs.o -obj-$(CONFIG_ACPI_SBS) += i2c_ec.o sbs.o +obj-$(CONFIG_ACPI_SBS) += sbs.o diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index e64c76c..fc9c50a 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -44,7 +44,7 @@ #define ACPI_BATTERY_CLASS "battery" #define ACPI_BATTERY_HID "PNP0C0A" #define ACPI_BATTERY_DEVICE_NAME "Battery" #define ACPI_BATTERY_FILE_INFO "info" -#define ACPI_BATTERY_FILE_STATUS "state" +#define ACPI_BATTERY_FILE_STATE "state" #define ACPI_BATTERY_FILE_ALARM "alarm" #define ACPI_BATTERY_NOTIFY_STATUS 0x80 #define ACPI_BATTERY_NOTIFY_INFO 0x81 @@ -52,12 +52,24 @@ #define ACPI_BATTERY_UNITS_WATTS "mW" #define ACPI_BATTERY_UNITS_AMPS "mA" #define _COMPONENT ACPI_BATTERY_COMPONENT + +#define ACPI_BATTERY_UPDATE_TIME 0 + +#define ACPI_BATTERY_NONE_UPDATE 0 +#define ACPI_BATTERY_EASY_UPDATE 1 +#define ACPI_BATTERY_INIT_UPDATE 2 + ACPI_MODULE_NAME("battery"); MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI Battery Driver"); MODULE_LICENSE("GPL"); +static unsigned int update_time = ACPI_BATTERY_UPDATE_TIME; + +/* 0 - every time, > 0 - by update_time */ +module_param(update_time, uint, 0644); + extern struct proc_dir_entry *acpi_lock_battery_dir(void); extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); @@ -76,7 +88,7 @@ static struct acpi_driver acpi_battery_d }, }; -struct acpi_battery_status { +struct acpi_battery_state { acpi_integer state; acpi_integer present_rate; acpi_integer remaining_capacity; @@ -100,32 +112,117 @@ struct acpi_battery_info { }; struct acpi_battery_flags { - u8 present:1; /* Bay occupied? */ - u8 power_unit:1; /* 0=watts, 1=apms */ - u8 alarm:1; /* _BTP present? */ - u8 reserved:5; -}; - -struct acpi_battery_trips { - unsigned long warning; - unsigned long low; + u8 battery_present_prev; + u8 alarm_present; + u8 init_update; + u8 info_update; + u8 state_update; + u8 alarm_update; + u8 power_unit; }; struct acpi_battery { - struct acpi_device * device; + struct mutex mutex; + struct acpi_device *device; struct acpi_battery_flags flags; - struct acpi_battery_trips trips; + struct acpi_buffer bif_data; + struct acpi_buffer bst_data; unsigned long alarm; - struct acpi_battery_info *info; + unsigned long info_update_time; + unsigned long state_update_time; + unsigned long alarm_update_time; }; +#define acpi_battery_present(battery) battery->device->status.battery_present +#define acpi_battery_present_prev(battery) battery->flags.battery_present_prev +#define acpi_battery_alarm_present(battery) battery->flags.alarm_present +#define acpi_battery_init_update_flag(battery) battery->flags.init_update +#define acpi_battery_info_update_flag(battery) battery->flags.info_update +#define acpi_battery_state_update_flag(battery) battery->flags.state_update +#define acpi_battery_alarm_update_flag(battery) battery->flags.alarm_update +#define acpi_battery_power_units(battery) battery->flags.power_unit ? \ + ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS +#define acpi_battery_handle(battery) battery->device->handle +#define acpi_battery_inserted(battery) (!acpi_battery_present_prev(battery) & acpi_battery_present(battery)) +#define acpi_battery_removed(battery) (acpi_battery_present_prev(battery) & !acpi_battery_present(battery)) +#define acpi_battery_bid(battery) acpi_device_bid(battery->device) +#define acpi_battery_status_str(battery) acpi_battery_present(battery) ? "present" : "absent" + /* -------------------------------------------------------------------------- Battery Management -------------------------------------------------------------------------- */ -static int -acpi_battery_get_info(struct acpi_battery *battery, - struct acpi_battery_info **bif) +static void acpi_battery_mutex_lock(struct acpi_battery *battery) +{ + mutex_lock(&battery->mutex); +} + +static void acpi_battery_mutex_unlock(struct acpi_battery *battery) +{ + mutex_unlock(&battery->mutex); +} + +static void acpi_battery_check_result(struct acpi_battery *battery, int result) +{ + if (!battery) + return; + + if (result) { + acpi_battery_init_update_flag(battery) = 1; + } +} + +static int acpi_battery_extract_package(struct acpi_battery *battery, + union acpi_object *package, + struct acpi_buffer *format, + struct acpi_buffer *data, + char *package_name) +{ + acpi_status status = AE_OK; + struct acpi_buffer data_null = { 0, NULL }; + + status = acpi_extract_package(package, format, &data_null); + if (status != AE_BUFFER_OVERFLOW) { + ACPI_EXCEPTION((AE_INFO, status, "Extracting size %s", + package_name)); + return -ENODEV; + } + + if (data_null.length != data->length) { + if (data->pointer) { + kfree(data->pointer); + } + data->pointer = kzalloc(data_null.length, GFP_KERNEL); + if (!data->pointer) { + ACPI_EXCEPTION((AE_INFO, AE_NO_MEMORY, "kzalloc()")); + return -ENOMEM; + } + data->length = data_null.length; + } + + status = acpi_extract_package(package, format, data); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Extracting %s", + package_name)); + return -ENODEV; + } + + return 0; +} + +static int acpi_battery_get_status(struct acpi_battery *battery) +{ + int result = 0; + + result = acpi_bus_get_status(battery->device); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "Evaluating _STA")); + return -ENODEV; + } + return result; +} + +static int acpi_battery_get_info(struct acpi_battery *battery) { int result = 0; acpi_status status = 0; @@ -133,16 +230,20 @@ acpi_battery_get_info(struct acpi_batter struct acpi_buffer format = { sizeof(ACPI_BATTERY_FORMAT_BIF), ACPI_BATTERY_FORMAT_BIF }; - struct acpi_buffer data = { 0, NULL }; union acpi_object *package = NULL; + struct acpi_buffer *data = NULL; + struct acpi_battery_info *bif = NULL; + battery->info_update_time = get_seconds(); - if (!battery || !bif) - return -EINVAL; + if (!acpi_battery_present(battery)) + return 0; /* Evalute _BIF */ - status = acpi_evaluate_object(battery->device->handle, "_BIF", NULL, &buffer); + status = + acpi_evaluate_object(acpi_battery_handle(battery), "_BIF", NULL, + &buffer); if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BIF")); return -ENODEV; @@ -150,41 +251,31 @@ acpi_battery_get_info(struct acpi_batter package = buffer.pointer; + data = &battery->bif_data; + /* Extract Package Data */ - status = acpi_extract_package(package, &format, &data); - if (status != AE_BUFFER_OVERFLOW) { - ACPI_EXCEPTION((AE_INFO, status, "Extracting _BIF")); - result = -ENODEV; + result = + acpi_battery_extract_package(battery, package, &format, data, + "_BIF"); + if (result) goto end; - } - data.pointer = kzalloc(data.length, GFP_KERNEL); - if (!data.pointer) { - result = -ENOMEM; - goto end; - } + end: - status = acpi_extract_package(package, &format, &data); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Extracting _BIF")); - kfree(data.pointer); - result = -ENODEV; - goto end; + if (buffer.pointer) { + kfree(buffer.pointer); } - end: - kfree(buffer.pointer); - - if (!result) - (*bif) = data.pointer; + if (!result) { + bif = data->pointer; + battery->flags.power_unit = bif->power_unit; + } return result; } -static int -acpi_battery_get_status(struct acpi_battery *battery, - struct acpi_battery_status **bst) +static int acpi_battery_get_state(struct acpi_battery *battery) { int result = 0; acpi_status status = 0; @@ -192,16 +283,19 @@ acpi_battery_get_status(struct acpi_batt struct acpi_buffer format = { sizeof(ACPI_BATTERY_FORMAT_BST), ACPI_BATTERY_FORMAT_BST }; - struct acpi_buffer data = { 0, NULL }; union acpi_object *package = NULL; + struct acpi_buffer *data = NULL; + battery->state_update_time = get_seconds(); - if (!battery || !bst) - return -EINVAL; + if (!acpi_battery_present(battery)) + return 0; /* Evalute _BST */ - status = acpi_evaluate_object(battery->device->handle, "_BST", NULL, &buffer); + status = + acpi_evaluate_object(acpi_battery_handle(battery), "_BST", NULL, + &buffer); if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, "Evaluating _BST")); return -ENODEV; @@ -209,55 +303,51 @@ acpi_battery_get_status(struct acpi_batt package = buffer.pointer; - /* Extract Package Data */ + data = &battery->bst_data; - status = acpi_extract_package(package, &format, &data); - if (status != AE_BUFFER_OVERFLOW) { - ACPI_EXCEPTION((AE_INFO, status, "Extracting _BST")); - result = -ENODEV; - goto end; - } + /* Extract Package Data */ - data.pointer = kzalloc(data.length, GFP_KERNEL); - if (!data.pointer) { - result = -ENOMEM; + result = + acpi_battery_extract_package(battery, package, &format, data, + "_BST"); + if (result) goto end; - } - status = acpi_extract_package(package, &format, &data); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Extracting _BST")); - kfree(data.pointer); - result = -ENODEV; - goto end; + end: + if (buffer.pointer) { + kfree(buffer.pointer); } - end: - kfree(buffer.pointer); + return result; +} - if (!result) - (*bst) = data.pointer; +static int acpi_battery_get_alarm(struct acpi_battery *battery) +{ + battery->alarm_update_time = get_seconds(); - return result; + return 0; } -static int -acpi_battery_set_alarm(struct acpi_battery *battery, unsigned long alarm) +static int acpi_battery_set_alarm(struct acpi_battery *battery, + unsigned long alarm) { acpi_status status = 0; union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list arg_list = { 1, &arg0 }; + battery->alarm_update_time = get_seconds(); - if (!battery) - return -EINVAL; + if (!acpi_battery_present(battery)) + return -ENODEV; - if (!battery->flags.alarm) + if (!acpi_battery_alarm_present(battery)) return -ENODEV; arg0.integer.value = alarm; - status = acpi_evaluate_object(battery->device->handle, "_BTP", &arg_list, NULL); + status = + acpi_evaluate_object(acpi_battery_handle(battery), "_BTP", + &arg_list, NULL); if (ACPI_FAILURE(status)) return -ENODEV; @@ -268,65 +358,111 @@ acpi_battery_set_alarm(struct acpi_batte return 0; } -static int acpi_battery_check(struct acpi_battery *battery) +static int acpi_battery_init_alarm(struct acpi_battery *battery) { int result = 0; acpi_status status = AE_OK; acpi_handle handle = NULL; - struct acpi_device *device = NULL; - struct acpi_battery_info *bif = NULL; - + struct acpi_battery_info *bif = battery->bif_data.pointer; + unsigned long alarm = battery->alarm; - if (!battery) - return -EINVAL; + /* See if alarms are supported, and if so, set default */ - device = battery->device; + status = acpi_get_handle(acpi_battery_handle(battery), "_BTP", &handle); + if (ACPI_SUCCESS(status)) { + acpi_battery_alarm_present(battery) = 1; + if (!alarm && bif) { + alarm = bif->design_capacity_warning; + } + result = acpi_battery_set_alarm(battery, alarm); + if (result) + goto end; + } else { + acpi_battery_alarm_present(battery) = 0; + } - result = acpi_bus_get_status(device); - if (result) - return result; + end: - /* Insertion? */ + return result; +} - if (!battery->flags.present && device->status.battery_present) { +static int acpi_battery_init_update(struct acpi_battery *battery) +{ + int result = 0; - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Battery inserted\n")); + result = acpi_battery_get_status(battery); + if (result) + return result; - /* Evalute _BIF to get certain static information */ + acpi_battery_present_prev(battery) = acpi_battery_present(battery); - result = acpi_battery_get_info(battery, &bif); + if (acpi_battery_present(battery)) { + result = acpi_battery_get_info(battery); + if (result) + return result; + result = acpi_battery_get_state(battery); if (result) return result; - battery->flags.power_unit = bif->power_unit; - battery->trips.warning = bif->design_capacity_warning; - battery->trips.low = bif->design_capacity_low; - kfree(bif); + acpi_battery_init_alarm(battery); + } + + return result; +} + +static int acpi_battery_update(struct acpi_battery *battery, + int update, int *update_result_ptr) +{ + int result = 0; + int update_result = ACPI_BATTERY_NONE_UPDATE; - /* See if alarms are supported, and if so, set default */ + if (!acpi_battery_present(battery)) { + update = 1; + } - status = acpi_get_handle(battery->device->handle, "_BTP", &handle); - if (ACPI_SUCCESS(status)) { - battery->flags.alarm = 1; - acpi_battery_set_alarm(battery, battery->trips.warning); + if (acpi_battery_init_update_flag(battery)) { + result = acpi_battery_init_update(battery); + if (result) + goto end;; + update_result = ACPI_BATTERY_INIT_UPDATE; + } else if (update) { + result = acpi_battery_get_status(battery); + if (result) + goto end;; + if (acpi_battery_inserted(battery) + || acpi_battery_removed(battery)) { + result = acpi_battery_init_update(battery); + if (result) + goto end;; + update_result = ACPI_BATTERY_INIT_UPDATE; + } else { + update_result = ACPI_BATTERY_EASY_UPDATE; } } - /* Removal? */ + end: - else if (battery->flags.present && !device->status.battery_present) { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Battery removed\n")); - } + acpi_battery_init_update_flag(battery) = (result != 0); - battery->flags.present = device->status.battery_present; + *update_result_ptr = update_result; return result; } -static void acpi_battery_check_present(struct acpi_battery *battery) +static void acpi_battery_notify_update(struct acpi_battery *battery) { - if (!battery->flags.present) { - acpi_battery_check(battery); + acpi_battery_get_status(battery); + + if (acpi_battery_init_update_flag(battery)) { + return; + } + + if (acpi_battery_inserted(battery) || acpi_battery_removed(battery)) { + acpi_battery_init_update_flag(battery) = 1; + } else { + acpi_battery_info_update_flag(battery) = 1; + acpi_battery_state_update_flag(battery) = 1; + acpi_battery_alarm_update_flag(battery) = 1; } } @@ -335,37 +471,33 @@ static void acpi_battery_check_present(s -------------------------------------------------------------------------- */ static struct proc_dir_entry *acpi_battery_dir; -static int acpi_battery_read_info(struct seq_file *seq, void *offset) + +static int acpi_battery_read_info_print(struct seq_file *seq, int result) { - int result = 0; struct acpi_battery *battery = seq->private; struct acpi_battery_info *bif = NULL; char *units = "?"; - - if (!battery) + if (result) goto end; - acpi_battery_check_present(battery); - - if (battery->flags.present) + if (acpi_battery_present(battery)) seq_printf(seq, "present: yes\n"); else { seq_printf(seq, "present: no\n"); goto end; } - /* Battery Info (_BIF) */ - - result = acpi_battery_get_info(battery, &bif); - if (result || !bif) { - seq_printf(seq, "ERROR: Unable to read battery information\n"); + bif = battery->bif_data.pointer; + if (!bif) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "BIF buffer is NULL")); + result = -ENODEV; goto end; } - units = - bif-> - power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS; + /* Battery Units */ + + units = acpi_battery_power_units(battery); if (bif->design_capacity == ACPI_BATTERY_VALUE_UNKNOWN) seq_printf(seq, "design capacity: unknown\n"); @@ -396,7 +528,6 @@ static int acpi_battery_read_info(struct else seq_printf(seq, "design voltage: %d mV\n", (u32) bif->design_voltage); - seq_printf(seq, "design capacity warning: %d %sh\n", (u32) bif->design_capacity_warning, units); seq_printf(seq, "design capacity low: %d %sh\n", @@ -411,9 +542,48 @@ static int acpi_battery_read_info(struct seq_printf(seq, "OEM info: %s\n", bif->oem_info); end: - kfree(bif); - return 0; + if (result) + seq_printf(seq, "ERROR: Unable to read battery info\n"); + + return result; +} + +static int acpi_battery_read_info(struct seq_file *seq, void *offset) +{ + struct acpi_battery *battery = seq->private; + int result = 0; + int update_result = ACPI_BATTERY_NONE_UPDATE; + int update = 0; + + acpi_battery_mutex_lock(battery); + + update = (get_seconds() - battery->info_update_time >= update_time); + update = (update | acpi_battery_info_update_flag(battery)); + + result = acpi_battery_update(battery, update, &update_result); + if (result) + goto end; + + /* Battery Info (_BIF) */ + + if (update_result == ACPI_BATTERY_EASY_UPDATE) { + result = acpi_battery_get_info(battery); + if (result) + goto end; + } + + end: + + result = acpi_battery_read_info_print(seq, result); + + acpi_battery_check_result(battery, result); + + acpi_battery_info_update_flag(battery) = result; + + acpi_battery_mutex_unlock(battery); + + return result; } static int acpi_battery_info_open_fs(struct inode *inode, struct file *file) @@ -421,40 +591,33 @@ static int acpi_battery_info_open_fs(str return single_open(file, acpi_battery_read_info, PDE(inode)->data); } -static int acpi_battery_read_state(struct seq_file *seq, void *offset) +static int acpi_battery_read_state_print(struct seq_file *seq, int result) { - int result = 0; struct acpi_battery *battery = seq->private; - struct acpi_battery_status *bst = NULL; + struct acpi_battery_state *bst = NULL; char *units = "?"; - - if (!battery) + if (result) goto end; - acpi_battery_check_present(battery); - - if (battery->flags.present) + if (acpi_battery_present(battery)) seq_printf(seq, "present: yes\n"); else { seq_printf(seq, "present: no\n"); goto end; } - /* Battery Units */ - - units = - battery->flags. - power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS; - - /* Battery Status (_BST) */ - - result = acpi_battery_get_status(battery, &bst); - if (result || !bst) { - seq_printf(seq, "ERROR: Unable to read battery status\n"); + bst = battery->bst_data.pointer; + if (!bst) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "BST buffer is NULL")); + result = -ENODEV; goto end; } + /* Battery Units */ + + units = acpi_battery_power_units(battery); + if (!(bst->state & 0x04)) seq_printf(seq, "capacity state: ok\n"); else @@ -490,9 +653,49 @@ static int acpi_battery_read_state(struc (u32) bst->present_voltage); end: - kfree(bst); - return 0; + if (result) { + seq_printf(seq, "ERROR: Unable to read battery state\n"); + } + + return result; +} + +static int acpi_battery_read_state(struct seq_file *seq, void *offset) +{ + struct acpi_battery *battery = seq->private; + int result = 0; + int update_result = ACPI_BATTERY_NONE_UPDATE; + int update = 0; + + acpi_battery_mutex_lock(battery); + + update = (get_seconds() - battery->state_update_time >= update_time); + update = (update | acpi_battery_state_update_flag(battery)); + + result = acpi_battery_update(battery, update, &update_result); + if (result) + goto end; + + /* Battery State (_BST) */ + + if (update_result == ACPI_BATTERY_EASY_UPDATE) { + result = acpi_battery_get_state(battery); + if (result) + goto end; + } + + end: + + result = acpi_battery_read_state_print(seq, result); + + acpi_battery_check_result(battery, result); + + acpi_battery_state_update_flag(battery) = result; + + acpi_battery_mutex_unlock(battery); + + return result; } static int acpi_battery_state_open_fs(struct inode *inode, struct file *file) @@ -500,38 +703,72 @@ static int acpi_battery_state_open_fs(st return single_open(file, acpi_battery_read_state, PDE(inode)->data); } -static int acpi_battery_read_alarm(struct seq_file *seq, void *offset) +static int acpi_battery_read_alarm_print(struct seq_file *seq, int result) { struct acpi_battery *battery = seq->private; char *units = "?"; - - if (!battery) + if (result) goto end; - acpi_battery_check_present(battery); - - if (!battery->flags.present) { + if (!acpi_battery_present(battery)) { seq_printf(seq, "present: no\n"); goto end; } /* Battery Units */ - units = - battery->flags. - power_unit ? ACPI_BATTERY_UNITS_AMPS : ACPI_BATTERY_UNITS_WATTS; - - /* Battery Alarm */ + units = acpi_battery_power_units(battery); seq_printf(seq, "alarm: "); if (!battery->alarm) seq_printf(seq, "unsupported\n"); else - seq_printf(seq, "%d %sh\n", (u32) battery->alarm, units); + seq_printf(seq, "%lu %sh\n", battery->alarm, units); end: - return 0; + + if (result) + seq_printf(seq, "ERROR: Unable to read battery alarm\n"); + + return result; +} + +static int acpi_battery_read_alarm(struct seq_file *seq, void *offset) +{ + struct acpi_battery *battery = seq->private; + int result = 0; + int update_result = ACPI_BATTERY_NONE_UPDATE; + int update = 0; + + acpi_battery_mutex_lock(battery); + + update = (get_seconds() - battery->alarm_update_time >= update_time); + update = (update | acpi_battery_alarm_update_flag(battery)); + + result = acpi_battery_update(battery, update, &update_result); + if (result) + goto end; + + /* Battery Alarm */ + + if (update_result == ACPI_BATTERY_EASY_UPDATE) { + result = acpi_battery_get_alarm(battery); + if (result) + goto end; + } + + end: + + result = acpi_battery_read_alarm_print(seq, result); + + acpi_battery_check_result(battery, result); + + acpi_battery_alarm_update_flag(battery) = result; + + acpi_battery_mutex_unlock(battery); + + return result; } static ssize_t @@ -543,27 +780,46 @@ acpi_battery_write_alarm(struct file *fi char alarm_string[12] = { '\0' }; struct seq_file *m = file->private_data; struct acpi_battery *battery = m->private; - + int update_result = ACPI_BATTERY_NONE_UPDATE; if (!battery || (count > sizeof(alarm_string) - 1)) return -EINVAL; - acpi_battery_check_present(battery); + acpi_battery_mutex_lock(battery); - if (!battery->flags.present) - return -ENODEV; + result = acpi_battery_update(battery, 1, &update_result); + if (result) { + result = -ENODEV; + goto end; + } - if (copy_from_user(alarm_string, buffer, count)) - return -EFAULT; + if (!acpi_battery_present(battery)) { + result = -ENODEV; + goto end; + } + + if (copy_from_user(alarm_string, buffer, count)) { + result = -EFAULT; + goto end; + } alarm_string[count] = '\0'; result = acpi_battery_set_alarm(battery, simple_strtoul(alarm_string, NULL, 0)); if (result) - return result; + goto end; - return count; + end: + + acpi_battery_check_result(battery, result); + + if (!result) + result = count; + + acpi_battery_mutex_unlock(battery); + + return result; } static int acpi_battery_alarm_open_fs(struct inode *inode, struct file *file) @@ -600,7 +856,6 @@ static int acpi_battery_add_fs(struct ac { struct proc_dir_entry *entry = NULL; - if (!acpi_device_dir(device)) { acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), acpi_battery_dir); @@ -621,7 +876,7 @@ static int acpi_battery_add_fs(struct ac } /* 'status' [R] */ - entry = create_proc_entry(ACPI_BATTERY_FILE_STATUS, + entry = create_proc_entry(ACPI_BATTERY_FILE_STATE, S_IRUGO, acpi_device_dir(device)); if (!entry) return -ENODEV; @@ -648,11 +903,10 @@ static int acpi_battery_add_fs(struct ac static int acpi_battery_remove_fs(struct acpi_device *device) { - if (acpi_device_dir(device)) { remove_proc_entry(ACPI_BATTERY_FILE_ALARM, acpi_device_dir(device)); - remove_proc_entry(ACPI_BATTERY_FILE_STATUS, + remove_proc_entry(ACPI_BATTERY_FILE_STATE, acpi_device_dir(device)); remove_proc_entry(ACPI_BATTERY_FILE_INFO, acpi_device_dir(device)); @@ -673,7 +927,6 @@ static void acpi_battery_notify(acpi_han struct acpi_battery *battery = data; struct acpi_device *device = NULL; - if (!battery) return; @@ -684,8 +937,12 @@ static void acpi_battery_notify(acpi_han case ACPI_BATTERY_NOTIFY_INFO: case ACPI_NOTIFY_BUS_CHECK: case ACPI_NOTIFY_DEVICE_CHECK: - acpi_battery_check(battery); - acpi_bus_generate_event(device, event, battery->flags.present); + acpi_battery_mutex_lock(battery); + device = battery->device; + acpi_battery_notify_update(battery); + acpi_battery_mutex_unlock(battery); + acpi_bus_generate_event(device, event, + acpi_battery_present(battery)); break; default: ACPI_DEBUG_PRINT((ACPI_DB_INFO, @@ -702,7 +959,6 @@ static int acpi_battery_add(struct acpi_ acpi_status status = 0; struct acpi_battery *battery = NULL; - if (!device) return -EINVAL; @@ -710,15 +966,21 @@ static int acpi_battery_add(struct acpi_ if (!battery) return -ENOMEM; + mutex_init(&battery->mutex); + + acpi_battery_mutex_lock(battery); + battery->device = device; strcpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_BATTERY_CLASS); acpi_driver_data(device) = battery; - result = acpi_battery_check(battery); + result = acpi_battery_get_status(battery); if (result) goto end; + acpi_battery_init_update_flag(battery) = 1; + result = acpi_battery_add_fs(device); if (result) goto end; @@ -727,6 +989,7 @@ static int acpi_battery_add(struct acpi_ ACPI_ALL_NOTIFY, acpi_battery_notify, battery); if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, "Installing notify handler")); result = -ENODEV; goto end; } @@ -736,11 +999,14 @@ static int acpi_battery_add(struct acpi_ device->status.battery_present ? "present" : "absent"); end: + if (result) { acpi_battery_remove_fs(device); kfree(battery); } + acpi_battery_mutex_unlock(battery); + return result; } @@ -749,18 +1015,29 @@ static int acpi_battery_remove(struct ac acpi_status status = 0; struct acpi_battery *battery = NULL; - if (!device || !acpi_driver_data(device)) return -EINVAL; battery = acpi_driver_data(device); + acpi_battery_mutex_lock(battery); + status = acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY, acpi_battery_notify); acpi_battery_remove_fs(device); + if (battery->bif_data.pointer) + kfree(battery->bif_data.pointer); + + if (battery->bst_data.pointer) + kfree(battery->bst_data.pointer); + + acpi_battery_mutex_unlock(battery); + + mutex_destroy(&battery->mutex); + kfree(battery); return 0; @@ -775,7 +1052,10 @@ static int acpi_battery_resume(struct ac return -EINVAL; battery = device->driver_data; - return acpi_battery_check(battery); + + acpi_battery_init_update_flag(battery) = 1; + + return 0; } static int __init acpi_battery_init(void) @@ -800,7 +1080,6 @@ static int __init acpi_battery_init(void static void __exit acpi_battery_exit(void) { - acpi_bus_unregister_driver(&acpi_battery_driver); acpi_unlock_battery_dir(acpi_battery_dir); diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 54a697f..811b1aa 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -667,6 +667,23 @@ static ssize_t write_undock(struct devic } DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); +/* + * show_dock_uid - read method for "uid" file in sysfs + */ +static ssize_t show_dock_uid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long lbuf; + acpi_status status = acpi_evaluate_integer(dock_station->handle, "_UID", 0, &lbuf); + if(ACPI_FAILURE(status)) { + return 0; + } + return snprintf(buf, PAGE_SIZE, "%lx\n", lbuf); +} +DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); + + + /** * dock_add - add a new dock station * @handle: the dock station handle @@ -715,6 +732,13 @@ static int dock_add(acpi_handle handle) kfree(dock_station); return ret; } + ret = device_create_file(&dock_device.dev, &dev_attr_uid); + if (ret) { + printk("Error %d adding sysfs file\n", ret); + platform_device_unregister(&dock_device); + kfree(dock_station); + return ret; + } /* Find dependent devices */ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index a802962..e08cf98 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -1,6 +1,8 @@ /* - * acpi_ec.c - ACPI Embedded Controller Driver ($Revision: 38 $) + * ec.c - ACPI Embedded Controller Driver (v2.0) * + * Copyright (C) 2006, 2007 Alexey Starikovskiy + * Copyright (C) 2006 Denis Sadykov * Copyright (C) 2004 Luming Yu * Copyright (C) 2001, 2002 Andy Grover * Copyright (C) 2001, 2002 Paul Diefenbaugh @@ -91,9 +93,9 @@ static struct acpi_driver acpi_ec_driver }; /* If we find an EC via the ECDT, we need to keep a ptr to its context */ +/* External interfaces use first EC only, so remember */ static struct acpi_ec { acpi_handle handle; - unsigned long uid; unsigned long gpe; unsigned long command_addr; unsigned long data_addr; @@ -101,12 +103,8 @@ static struct acpi_ec { struct mutex lock; atomic_t query_pending; atomic_t event_count; - atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */ wait_queue_head_t wait; -} *ec_ecdt; - -/* External interfaces use first EC only, so remember */ -static struct acpi_device *first_ec; +} *boot_ec, *first_ec; /* -------------------------------------------------------------------------- Transaction Management @@ -173,56 +171,6 @@ static int acpi_ec_wait(struct acpi_ec * return -ETIME; } -#ifdef ACPI_FUTURE_USAGE -/* - * Note: samsung nv5000 doesn't work with ec burst mode. - * http://bugzilla.kernel.org/show_bug.cgi?id=4980 - */ -int acpi_ec_enter_burst_mode(struct acpi_ec *ec) -{ - u8 tmp = 0; - u8 status = 0; - - status = acpi_ec_read_status(ec); - if (status != -EINVAL && !(status & ACPI_EC_FLAG_BURST)) { - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - if (status) - goto end; - acpi_ec_write_cmd(ec, ACPI_EC_BURST_ENABLE); - status = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1); - tmp = acpi_ec_read_data(ec); - if (tmp != 0x90) { /* Burst ACK byte */ - return -EINVAL; - } - } - - atomic_set(&ec->leaving_burst, 0); - return 0; - end: - ACPI_EXCEPTION((AE_INFO, status, "EC wait, burst mode")); - return -1; -} - -int acpi_ec_leave_burst_mode(struct acpi_ec *ec) -{ - u8 status = 0; - - status = acpi_ec_read_status(ec); - if (status != -EINVAL && (status & ACPI_EC_FLAG_BURST)) { - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - if (status) - goto end; - acpi_ec_write_cmd(ec, ACPI_EC_BURST_DISABLE); - acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); - } - atomic_set(&ec->leaving_burst, 1); - return 0; - end: - ACPI_EXCEPTION((AE_INFO, status, "EC leave burst mode")); - return -1; -} -#endif /* ACPI_FUTURE_USAGE */ - static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, const u8 * wdata, unsigned wdata_len, u8 * rdata, unsigned rdata_len) @@ -312,6 +260,21 @@ static int acpi_ec_transaction(struct ac return status; } +/* + * Note: samsung nv5000 doesn't work with ec burst mode. + * http://bugzilla.kernel.org/show_bug.cgi?id=4980 + */ +int acpi_ec_burst_enable(struct acpi_ec *ec) +{ + u8 d; + return acpi_ec_transaction(ec, ACPI_EC_BURST_ENABLE, NULL, 0, &d, 1); +} + +int acpi_ec_burst_disable(struct acpi_ec *ec) +{ + return acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, NULL, 0, NULL, 0); +} + static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) { int result; @@ -333,18 +296,33 @@ static int acpi_ec_write(struct acpi_ec /* * Externally callable EC access functions. For now, assume 1 EC only */ +int ec_burst_enable(void) +{ + if (!first_ec) + return -ENODEV; + return acpi_ec_burst_enable(first_ec); +} + +EXPORT_SYMBOL(ec_burst_enable); + +int ec_burst_disable(void) +{ + if (!first_ec) + return -ENODEV; + return acpi_ec_burst_disable(first_ec); +} + +EXPORT_SYMBOL(ec_burst_disable); + int ec_read(u8 addr, u8 * val) { - struct acpi_ec *ec; int err; u8 temp_data; if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - err = acpi_ec_read(ec, addr, &temp_data); + err = acpi_ec_read(first_ec, addr, &temp_data); if (!err) { *val = temp_data; @@ -357,15 +335,12 @@ EXPORT_SYMBOL(ec_read); int ec_write(u8 addr, u8 val) { - struct acpi_ec *ec; int err; if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - err = acpi_ec_write(ec, addr, val); + err = acpi_ec_write(first_ec, addr, val); return err; } @@ -376,14 +351,10 @@ int ec_transaction(u8 command, const u8 * wdata, unsigned wdata_len, u8 * rdata, unsigned rdata_len) { - struct acpi_ec *ec; - if (!first_ec) return -ENODEV; - ec = acpi_driver_data(first_ec); - - return acpi_ec_transaction(ec, command, wdata, + return acpi_ec_transaction(first_ec, command, wdata, wdata_len, rdata, rdata_len); } @@ -420,7 +391,7 @@ static int acpi_ec_query(struct acpi_ec static void acpi_ec_gpe_query(void *ec_cxt) { - struct acpi_ec *ec = (struct acpi_ec *)ec_cxt; + struct acpi_ec *ec = ec_cxt; u8 value = 0; char object_name[8]; @@ -438,8 +409,9 @@ static u32 acpi_ec_gpe_handler(void *dat { acpi_status status = AE_OK; u8 value; - struct acpi_ec *ec = (struct acpi_ec *)data; + struct acpi_ec *ec = data; atomic_inc(&ec->event_count); + if (acpi_ec_mode == EC_INTR) { wake_up(&ec->wait); } @@ -482,7 +454,7 @@ acpi_ec_space_handler(u32 function, void *handler_context, void *region_context) { int result = 0; - struct acpi_ec *ec = NULL; + struct acpi_ec *ec = handler_context; u64 temp = *value; acpi_integer f_v = 0; int i = 0; @@ -494,8 +466,6 @@ acpi_ec_space_handler(u32 function, return AE_BAD_PARAMETER; } - ec = (struct acpi_ec *)handler_context; - next_byte: switch (function) { case ACPI_READ: @@ -551,18 +521,16 @@ static struct proc_dir_entry *acpi_ec_di static int acpi_ec_read_info(struct seq_file *seq, void *offset) { - struct acpi_ec *ec = (struct acpi_ec *)seq->private; + struct acpi_ec *ec = seq->private; if (!ec) goto end; - seq_printf(seq, "gpe: 0x%02x\n", (u32) ec->gpe); - seq_printf(seq, "ports: 0x%02x, 0x%02x\n", - (u32) ec->command_addr, (u32) ec->data_addr); - seq_printf(seq, "use global lock: %s\n", + seq_printf(seq, "gpe:\t\t\t0x%02x\n", (u32) ec->gpe); + seq_printf(seq, "ports:\t\t\t0x%02x, 0x%02x\n", + (unsigned)ec->command_addr, (unsigned)ec->data_addr); + seq_printf(seq, "use global lock:\t%s\n", ec->global_lock ? "yes" : "no"); - acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - end: return 0; } @@ -619,154 +587,122 @@ static int acpi_ec_remove_fs(struct acpi /* -------------------------------------------------------------------------- Driver Interface -------------------------------------------------------------------------- */ +static acpi_status +ec_parse_io_ports(struct acpi_resource *resource, void *context); + +static acpi_status +ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval); + +static struct acpi_ec *make_acpi_ec(void) +{ + struct acpi_ec *ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); + if (!ec) + return NULL; + + atomic_set(&ec->query_pending, 1); + atomic_set(&ec->event_count, 1); + mutex_init(&ec->lock); + init_waitqueue_head(&ec->wait); + + return ec; +} static int acpi_ec_add(struct acpi_device *device) { - int result = 0; acpi_status status = AE_OK; struct acpi_ec *ec = NULL; if (!device) return -EINVAL; - ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec) - return -ENOMEM; - - ec->handle = device->handle; - ec->uid = -1; - mutex_init(&ec->lock); - atomic_set(&ec->query_pending, 0); - atomic_set(&ec->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - atomic_set(&ec->leaving_burst, 1); - init_waitqueue_head(&ec->wait); - } strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_EC_CLASS); - acpi_driver_data(device) = ec; - - /* Use the global lock for all EC transactions? */ - acpi_evaluate_integer(ec->handle, "_GLK", NULL, &ec->global_lock); - - /* XXX we don't test uids, because on some boxes ecdt uid = 0, see: - http://bugzilla.kernel.org/show_bug.cgi?id=6111 */ - if (ec_ecdt) { - acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, - ACPI_ADR_SPACE_EC, - &acpi_ec_space_handler); - acpi_remove_gpe_handler(NULL, ec_ecdt->gpe, - &acpi_ec_gpe_handler); + ec = make_acpi_ec(); + if (!ec) + return -ENOMEM; - kfree(ec_ecdt); + status = ec_parse_device(device->handle, 0, ec, NULL); + if (status != AE_CTRL_TERMINATE) { + kfree(ec); + return -EINVAL; } - /* Get GPE bit assignment (EC events). */ - /* TODO: Add support for _GPE returning a package */ - status = acpi_evaluate_integer(ec->handle, "_GPE", NULL, &ec->gpe); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Obtaining GPE bit assignment")); - result = -ENODEV; - goto end; - } + /* Check if we found the boot EC */ + if (boot_ec) { + if (boot_ec->gpe == ec->gpe) { + /* We might have incorrect info for GL at boot time */ + mutex_lock(&boot_ec->lock); + boot_ec->global_lock = ec->global_lock; + mutex_unlock(&boot_ec->lock); + kfree(ec); + ec = boot_ec; + } + } else + first_ec = ec; + ec->handle = device->handle; + acpi_driver_data(device) = ec; - result = acpi_ec_add_fs(device); - if (result) - goto end; + acpi_ec_add_fs(device); ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s [%s] (gpe %d) interrupt mode.", acpi_device_name(device), acpi_device_bid(device), (u32) ec->gpe)); - if (!first_ec) - first_ec = device; - - end: - if (result) - kfree(ec); - - return result; + return 0; } static int acpi_ec_remove(struct acpi_device *device, int type) { - struct acpi_ec *ec = NULL; + struct acpi_ec *ec; if (!device) return -EINVAL; ec = acpi_driver_data(device); - acpi_ec_remove_fs(device); + acpi_driver_data(device) = NULL; + if (ec == first_ec) + first_ec = NULL; - kfree(ec); - + /* Don't touch boot EC */ + if (boot_ec != ec) + kfree(ec); return 0; } static acpi_status -acpi_ec_io_ports(struct acpi_resource *resource, void *context) +ec_parse_io_ports(struct acpi_resource *resource, void *context) { - struct acpi_ec *ec = (struct acpi_ec *)context; + struct acpi_ec *ec = context; - if (resource->type != ACPI_RESOURCE_TYPE_IO) { + if (resource->type != ACPI_RESOURCE_TYPE_IO) return AE_OK; - } /* * The first address region returned is the data port, and * the second address region returned is the status/command * port. */ - if (ec->data_addr == 0) { + if (ec->data_addr == 0) ec->data_addr = resource->data.io.minimum; - } else if (ec->command_addr == 0) { + else if (ec->command_addr == 0) ec->command_addr = resource->data.io.minimum; - } else { + else return AE_CTRL_TERMINATE; - } return AE_OK; } -static int acpi_ec_start(struct acpi_device *device) +static int ec_install_handlers(struct acpi_ec *ec) { - acpi_status status = AE_OK; - struct acpi_ec *ec = NULL; - - if (!device) - return -EINVAL; - - ec = acpi_driver_data(device); - - if (!ec) - return -EINVAL; - - /* - * Get I/O port addresses. Convert to GAS format. - */ - status = acpi_walk_resources(ec->handle, METHOD_NAME__CRS, - acpi_ec_io_ports, ec); - if (ACPI_FAILURE(status) || ec->command_addr == 0) { - ACPI_EXCEPTION((AE_INFO, status, - "Error getting I/O port addresses")); - return -ENODEV; - } - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx", - ec->gpe, ec->command_addr, ec->data_addr)); - - /* - * Install GPE handler - */ + acpi_status status; status = acpi_install_gpe_handler(NULL, ec->gpe, ACPI_GPE_EDGE_TRIGGERED, &acpi_ec_gpe_handler, ec); - if (ACPI_FAILURE(status)) { + if (ACPI_FAILURE(status)) return -ENODEV; - } + acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME); acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); @@ -779,18 +715,49 @@ static int acpi_ec_start(struct acpi_dev return -ENODEV; } - return AE_OK; + /* EC is fully operational, allow queries */ + atomic_set(&ec->query_pending, 0); + + return 0; +} + +static int acpi_ec_start(struct acpi_device *device) +{ + struct acpi_ec *ec; + + if (!device) + return -EINVAL; + + ec = acpi_driver_data(device); + + if (!ec) + return -EINVAL; + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx", + ec->gpe, ec->command_addr, ec->data_addr)); + + /* Boot EC is already working */ + if (ec == boot_ec) + return 0; + + return ec_install_handlers(ec); } static int acpi_ec_stop(struct acpi_device *device, int type) { - acpi_status status = AE_OK; - struct acpi_ec *ec = NULL; + acpi_status status; + struct acpi_ec *ec; if (!device) return -EINVAL; ec = acpi_driver_data(device); + if (!ec) + return -EINVAL; + + /* Don't touch boot EC */ + if (ec == boot_ec) + return 0; status = acpi_remove_address_space_handler(ec->handle, ACPI_ADR_SPACE_EC, @@ -805,164 +772,67 @@ static int acpi_ec_stop(struct acpi_devi return 0; } -static acpi_status __init -acpi_fake_ecdt_callback(acpi_handle handle, - u32 Level, void *context, void **retval) +static acpi_status +ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval) { acpi_status status; - mutex_init(&ec_ecdt->lock); - atomic_set(&ec_ecdt->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - init_waitqueue_head(&ec_ecdt->wait); - } + struct acpi_ec *ec = context; status = acpi_walk_resources(handle, METHOD_NAME__CRS, - acpi_ec_io_ports, ec_ecdt); + ec_parse_io_ports, ec); if (ACPI_FAILURE(status)) return status; - ec_ecdt->uid = -1; - acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->uid); - - status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec_ecdt->gpe); + /* Get GPE bit assignment (EC events). */ + /* TODO: Add support for _GPE returning a package */ + status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec->gpe); if (ACPI_FAILURE(status)) return status; - ec_ecdt->global_lock = TRUE; - ec_ecdt->handle = handle; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx", - ec_ecdt->gpe, ec_ecdt->command_addr, - ec_ecdt->data_addr)); - return AE_CTRL_TERMINATE; -} - -/* - * Some BIOS (such as some from Gateway laptops) access EC region very early - * such as in BAT0._INI or EC._INI before an EC device is found and - * do not provide an ECDT. According to ACPI spec, ECDT isn't mandatorily - * required, but if EC regison is accessed early, it is required. - * The routine tries to workaround the BIOS bug by pre-scan EC device - * It assumes that _CRS, _HID, _GPE, _UID methods of EC don't touch any - * op region (since _REG isn't invoked yet). The assumption is true for - * all systems found. - */ -static int __init acpi_ec_fake_ecdt(void) -{ - acpi_status status; - int ret = 0; + /* Use the global lock for all EC transactions? */ + acpi_evaluate_integer(handle, "_GLK", NULL, &ec->global_lock); - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Try to make an fake ECDT")); + ec->handle = handle; - ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec_ecdt) { - ret = -ENOMEM; - goto error; - } + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx", + ec->gpe, ec->command_addr, ec->data_addr)); - status = acpi_get_devices(ACPI_EC_HID, - acpi_fake_ecdt_callback, NULL, NULL); - if (ACPI_FAILURE(status)) { - kfree(ec_ecdt); - ec_ecdt = NULL; - ret = -ENODEV; - ACPI_EXCEPTION((AE_INFO, status, "Can't make an fake ECDT")); - goto error; - } - return 0; - error: - return ret; + return AE_CTRL_TERMINATE; } -static int __init acpi_ec_get_real_ecdt(void) +int __init acpi_ec_ecdt_probe(void) { + int ret; acpi_status status; struct acpi_table_ecdt *ecdt_ptr; - status = acpi_get_table(ACPI_SIG_ECDT, 1, - (struct acpi_table_header **)&ecdt_ptr); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT")); - + boot_ec = make_acpi_ec(); + if (!boot_ec) + return -ENOMEM; /* - * Generate a temporary ec context to use until the namespace is scanned + * Generate a boot ec context */ - ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); - if (!ec_ecdt) - return -ENOMEM; - mutex_init(&ec_ecdt->lock); - atomic_set(&ec_ecdt->event_count, 1); - if (acpi_ec_mode == EC_INTR) { - init_waitqueue_head(&ec_ecdt->wait); - } - ec_ecdt->command_addr = ecdt_ptr->control.address; - ec_ecdt->data_addr = ecdt_ptr->data.address; - ec_ecdt->gpe = ecdt_ptr->gpe; - /* use the GL just to be safe */ - ec_ecdt->global_lock = TRUE; - ec_ecdt->uid = ecdt_ptr->uid; - - status = acpi_get_handle(NULL, ecdt_ptr->id, &ec_ecdt->handle); - if (ACPI_FAILURE(status)) { + status = acpi_get_table(ACPI_SIG_ECDT, 1, + (struct acpi_table_header **)&ecdt_ptr); + if (ACPI_FAILURE(status)) goto error; - } - - return 0; - error: - ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT")); - kfree(ec_ecdt); - ec_ecdt = NULL; - return -ENODEV; -} - -static int __initdata acpi_fake_ecdt_enabled; -int __init acpi_ec_ecdt_probe(void) -{ - acpi_status status; - int ret; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT")); - ret = acpi_ec_get_real_ecdt(); - /* Try to make a fake ECDT */ - if (ret && acpi_fake_ecdt_enabled) { - ret = acpi_ec_fake_ecdt(); - } + boot_ec->command_addr = ecdt_ptr->control.address; + boot_ec->data_addr = ecdt_ptr->data.address; + boot_ec->gpe = ecdt_ptr->gpe; + boot_ec->handle = ACPI_ROOT_OBJECT; - if (ret) + ret = ec_install_handlers(boot_ec); + if (!ret) { + first_ec = boot_ec; return 0; - - /* - * Install GPE handler - */ - status = acpi_install_gpe_handler(NULL, ec_ecdt->gpe, - ACPI_GPE_EDGE_TRIGGERED, - &acpi_ec_gpe_handler, ec_ecdt); - if (ACPI_FAILURE(status)) { - goto error; } - acpi_set_gpe_type(NULL, ec_ecdt->gpe, ACPI_GPE_TYPE_RUNTIME); - acpi_enable_gpe(NULL, ec_ecdt->gpe, ACPI_NOT_ISR); - - status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, - ACPI_ADR_SPACE_EC, - &acpi_ec_space_handler, - &acpi_ec_space_setup, - ec_ecdt); - if (ACPI_FAILURE(status)) { - acpi_remove_gpe_handler(NULL, ec_ecdt->gpe, - &acpi_ec_gpe_handler); - goto error; - } - - return 0; - error: - ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT")); - kfree(ec_ecdt); - ec_ecdt = NULL; + kfree(boot_ec); + boot_ec = NULL; return -ENODEV; } @@ -1003,13 +873,6 @@ static void __exit acpi_ec_exit(void) } #endif /* 0 */ -static int __init acpi_fake_ecdt_setup(char *str) -{ - acpi_fake_ecdt_enabled = 1; - return 1; -} - -__setup("acpi_fake_ecdt", acpi_fake_ecdt_setup); static int __init acpi_ec_set_intr_mode(char *str) { int intr; @@ -1017,12 +880,8 @@ static int __init acpi_ec_set_intr_mode( if (!get_option(&str, &intr)) return 0; - if (intr) { - acpi_ec_mode = EC_INTR; - } else { - acpi_ec_mode = EC_POLL; - } - acpi_ec_driver.ops.add = acpi_ec_add; + acpi_ec_mode = (intr) ? EC_INTR : EC_POLL; + printk(KERN_NOTICE PREFIX "%s mode.\n", intr ? "interrupt" : "polling"); return 1; diff --git a/drivers/acpi/i2c_ec.c b/drivers/acpi/i2c_ec.c deleted file mode 100644 index acab4a4..0000000 --- a/drivers/acpi/i2c_ec.c +++ /dev/null @@ -1,403 +0,0 @@ -/* - * SMBus driver for ACPI Embedded Controller ($Revision: 1.3 $) - * - * Copyright (c) 2002, 2005 Ducrot Bruno - * Copyright (c) 2005 Rich Townsend (tiny hacks & tweaks) - * - * 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 version 2. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "i2c_ec.h" - -#define xudelay(t) udelay(t) -#define xmsleep(t) msleep(t) - -#define ACPI_EC_HC_COMPONENT 0x00080000 -#define ACPI_EC_HC_CLASS "ec_hc_smbus" -#define ACPI_EC_HC_HID "ACPI0001" -#define ACPI_EC_HC_DEVICE_NAME "EC HC smbus" - -#define _COMPONENT ACPI_EC_HC_COMPONENT - -ACPI_MODULE_NAME("i2c_ec"); - -static int acpi_ec_hc_add(struct acpi_device *device); -static int acpi_ec_hc_remove(struct acpi_device *device, int type); - -static struct acpi_driver acpi_ec_hc_driver = { - .name = "i2c_ec", - .class = ACPI_EC_HC_CLASS, - .ids = ACPI_EC_HC_HID, - .ops = { - .add = acpi_ec_hc_add, - .remove = acpi_ec_hc_remove, - }, -}; - -/* Various bit mask for EC_SC (R) */ -#define OBF 0x01 -#define IBF 0x02 -#define CMD 0x08 -#define BURST 0x10 -#define SCI_EVT 0x20 -#define SMI_EVT 0x40 - -/* Commands for EC_SC (W) */ -#define RD_EC 0x80 -#define WR_EC 0x81 -#define BE_EC 0x82 -#define BD_EC 0x83 -#define QR_EC 0x84 - -/* - * ACPI 2.0 chapter 13 SMBus 2.0 EC register model - */ - -#define ACPI_EC_SMB_PRTCL 0x00 /* protocol, PEC */ -#define ACPI_EC_SMB_STS 0x01 /* status */ -#define ACPI_EC_SMB_ADDR 0x02 /* address */ -#define ACPI_EC_SMB_CMD 0x03 /* command */ -#define ACPI_EC_SMB_DATA 0x04 /* 32 data registers */ -#define ACPI_EC_SMB_BCNT 0x24 /* number of data bytes */ -#define ACPI_EC_SMB_ALRM_A 0x25 /* alarm address */ -#define ACPI_EC_SMB_ALRM_D 0x26 /* 2 bytes alarm data */ - -#define ACPI_EC_SMB_STS_DONE 0x80 -#define ACPI_EC_SMB_STS_ALRM 0x40 -#define ACPI_EC_SMB_STS_RES 0x20 -#define ACPI_EC_SMB_STS_STATUS 0x1f - -#define ACPI_EC_SMB_STATUS_OK 0x00 -#define ACPI_EC_SMB_STATUS_FAIL 0x07 -#define ACPI_EC_SMB_STATUS_DNAK 0x10 -#define ACPI_EC_SMB_STATUS_DERR 0x11 -#define ACPI_EC_SMB_STATUS_CMD_DENY 0x12 -#define ACPI_EC_SMB_STATUS_UNKNOWN 0x13 -#define ACPI_EC_SMB_STATUS_ACC_DENY 0x17 -#define ACPI_EC_SMB_STATUS_TIMEOUT 0x18 -#define ACPI_EC_SMB_STATUS_NOTSUP 0x19 -#define ACPI_EC_SMB_STATUS_BUSY 0x1A -#define ACPI_EC_SMB_STATUS_PEC 0x1F - -#define ACPI_EC_SMB_PRTCL_WRITE 0x00 -#define ACPI_EC_SMB_PRTCL_READ 0x01 -#define ACPI_EC_SMB_PRTCL_QUICK 0x02 -#define ACPI_EC_SMB_PRTCL_BYTE 0x04 -#define ACPI_EC_SMB_PRTCL_BYTE_DATA 0x06 -#define ACPI_EC_SMB_PRTCL_WORD_DATA 0x08 -#define ACPI_EC_SMB_PRTCL_BLOCK_DATA 0x0a -#define ACPI_EC_SMB_PRTCL_PROC_CALL 0x0c -#define ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL 0x0d -#define ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA 0x4a -#define ACPI_EC_SMB_PRTCL_PEC 0x80 - -/* Length of pre/post transaction sleep (msec) */ -#define ACPI_EC_SMB_TRANSACTION_SLEEP 1 -#define ACPI_EC_SMB_ACCESS_SLEEP1 1 -#define ACPI_EC_SMB_ACCESS_SLEEP2 10 - -static int acpi_ec_smb_read(struct acpi_ec_smbus *smbus, u8 address, u8 * data) -{ - u8 val; - int err; - - err = ec_read(smbus->base + address, &val); - if (!err) { - *data = val; - } - xmsleep(ACPI_EC_SMB_TRANSACTION_SLEEP); - return (err); -} - -static int acpi_ec_smb_write(struct acpi_ec_smbus *smbus, u8 address, u8 data) -{ - int err; - - err = ec_write(smbus->base + address, data); - return (err); -} - -static int -acpi_ec_smb_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, - char read_write, u8 command, int size, - union i2c_smbus_data *data) -{ - struct acpi_ec_smbus *smbus = adap->algo_data; - unsigned char protocol, len = 0, pec, temp[2] = { 0, 0 }; - int i; - - if (read_write == I2C_SMBUS_READ) { - protocol = ACPI_EC_SMB_PRTCL_READ; - } else { - protocol = ACPI_EC_SMB_PRTCL_WRITE; - } - pec = (flags & I2C_CLIENT_PEC) ? ACPI_EC_SMB_PRTCL_PEC : 0; - - switch (size) { - - case I2C_SMBUS_QUICK: - protocol |= ACPI_EC_SMB_PRTCL_QUICK; - read_write = I2C_SMBUS_WRITE; - break; - - case I2C_SMBUS_BYTE: - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte); - } - protocol |= ACPI_EC_SMB_PRTCL_BYTE; - break; - - case I2C_SMBUS_BYTE_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->byte); - } - protocol |= ACPI_EC_SMB_PRTCL_BYTE_DATA; - break; - - case I2C_SMBUS_WORD_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1, - data->word >> 8); - } - protocol |= ACPI_EC_SMB_PRTCL_WORD_DATA | pec; - break; - - case I2C_SMBUS_BLOCK_DATA: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - if (read_write == I2C_SMBUS_WRITE) { - len = min_t(u8, data->block[0], 32); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - for (i = 0; i < len; i++) - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - } - protocol |= ACPI_EC_SMB_PRTCL_BLOCK_DATA | pec; - break; - - case I2C_SMBUS_I2C_BLOCK_DATA: - len = min_t(u8, data->block[0], 32); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - if (read_write == I2C_SMBUS_WRITE) { - for (i = 0; i < len; i++) { - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - } - } - protocol |= ACPI_EC_SMB_PRTCL_I2C_BLOCK_DATA; - break; - - case I2C_SMBUS_PROC_CALL: - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA, data->word); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + 1, data->word >> 8); - protocol = ACPI_EC_SMB_PRTCL_PROC_CALL | pec; - read_write = I2C_SMBUS_READ; - break; - - case I2C_SMBUS_BLOCK_PROC_CALL: - protocol |= pec; - len = min_t(u8, data->block[0], 31); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_CMD, command); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_BCNT, len); - for (i = 0; i < len; i++) - acpi_ec_smb_write(smbus, ACPI_EC_SMB_DATA + i, - data->block[i + 1]); - protocol = ACPI_EC_SMB_PRTCL_BLOCK_PROC_CALL | pec; - read_write = I2C_SMBUS_READ; - break; - - default: - ACPI_DEBUG_PRINT((ACPI_DB_WARN, "EC SMBus adapter: " - "Unsupported transaction %d\n", size)); - return (-1); - } - - acpi_ec_smb_write(smbus, ACPI_EC_SMB_ADDR, addr << 1); - acpi_ec_smb_write(smbus, ACPI_EC_SMB_PRTCL, protocol); - - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - - if (~temp[0] & ACPI_EC_SMB_STS_DONE) { - xudelay(500); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - } - if (~temp[0] & ACPI_EC_SMB_STS_DONE) { - xmsleep(ACPI_EC_SMB_ACCESS_SLEEP2); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_STS, temp + 0); - } - if ((~temp[0] & ACPI_EC_SMB_STS_DONE) - || (temp[0] & ACPI_EC_SMB_STS_STATUS)) { - return (-1); - } - - if (read_write == I2C_SMBUS_WRITE) { - return (0); - } - - switch (size) { - - case I2C_SMBUS_BYTE: - case I2C_SMBUS_BYTE_DATA: - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, &data->byte); - break; - - case I2C_SMBUS_WORD_DATA: - case I2C_SMBUS_PROC_CALL: - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA, temp + 0); - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + 1, temp + 1); - data->word = (temp[1] << 8) | temp[0]; - break; - - case I2C_SMBUS_BLOCK_DATA: - case I2C_SMBUS_BLOCK_PROC_CALL: - len = 0; - acpi_ec_smb_read(smbus, ACPI_EC_SMB_BCNT, &len); - len = min_t(u8, len, 32); - case I2C_SMBUS_I2C_BLOCK_DATA: - for (i = 0; i < len; i++) - acpi_ec_smb_read(smbus, ACPI_EC_SMB_DATA + i, - data->block + i + 1); - data->block[0] = len; - break; - } - - return (0); -} - -static u32 acpi_ec_smb_func(struct i2c_adapter *adapter) -{ - - return (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BLOCK_DATA | - I2C_FUNC_SMBUS_PROC_CALL | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_HWPEC_CALC); -} - -static const struct i2c_algorithm acpi_ec_smbus_algorithm = { - .smbus_xfer = acpi_ec_smb_access, - .functionality = acpi_ec_smb_func, -}; - -static int acpi_ec_hc_add(struct acpi_device *device) -{ - int status; - unsigned long val; - struct acpi_ec_hc *ec_hc; - struct acpi_ec_smbus *smbus; - - if (!device) { - return -EINVAL; - } - - ec_hc = kzalloc(sizeof(struct acpi_ec_hc), GFP_KERNEL); - if (!ec_hc) { - return -ENOMEM; - } - - smbus = kzalloc(sizeof(struct acpi_ec_smbus), GFP_KERNEL); - if (!smbus) { - kfree(ec_hc); - return -ENOMEM; - } - - ec_hc->handle = device->handle; - strcpy(acpi_device_name(device), ACPI_EC_HC_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_EC_HC_CLASS); - acpi_driver_data(device) = ec_hc; - - status = acpi_evaluate_integer(ec_hc->handle, "_EC", NULL, &val); - if (ACPI_FAILURE(status)) { - ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Error obtaining _EC\n")); - kfree(ec_hc); - kfree(smbus); - return -EIO; - } - - smbus->ec = acpi_driver_data(device->parent); - smbus->base = (val & 0xff00ull) >> 8; - smbus->alert = val & 0xffull; - - smbus->adapter.owner = THIS_MODULE; - smbus->adapter.algo = &acpi_ec_smbus_algorithm; - smbus->adapter.algo_data = smbus; - smbus->adapter.dev.parent = &device->dev; - - if (i2c_add_adapter(&smbus->adapter)) { - ACPI_DEBUG_PRINT((ACPI_DB_WARN, - "EC SMBus adapter: Failed to register adapter\n")); - kfree(smbus); - kfree(ec_hc); - return -EIO; - } - - ec_hc->smbus = smbus; - - printk(KERN_INFO PREFIX "%s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); - - return AE_OK; -} - -static int acpi_ec_hc_remove(struct acpi_device *device, int type) -{ - struct acpi_ec_hc *ec_hc; - - if (!device) { - return -EINVAL; - } - ec_hc = acpi_driver_data(device); - - i2c_del_adapter(&ec_hc->smbus->adapter); - kfree(ec_hc->smbus); - kfree(ec_hc); - - return AE_OK; -} - -static int __init acpi_ec_hc_init(void) -{ - int result; - - result = acpi_bus_register_driver(&acpi_ec_hc_driver); - if (result < 0) { - return -ENODEV; - } - return 0; -} - -static void __exit acpi_ec_hc_exit(void) -{ - acpi_bus_unregister_driver(&acpi_ec_hc_driver); -} - -struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device) -{ - return acpi_driver_data(device->parent); -} - -EXPORT_SYMBOL(acpi_get_ec_hc); - -module_init(acpi_ec_hc_init); -module_exit(acpi_ec_hc_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ducrot Bruno"); -MODULE_DESCRIPTION("ACPI EC SMBus driver"); diff --git a/drivers/acpi/i2c_ec.h b/drivers/acpi/i2c_ec.h deleted file mode 100644 index 7c53fb7..0000000 --- a/drivers/acpi/i2c_ec.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SMBus driver for ACPI Embedded Controller ($Revision: 1.2 $) - * - * Copyright (c) 2002, 2005 Ducrot Bruno - * - * 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 version 2. - */ - -struct acpi_ec_smbus { - struct i2c_adapter adapter; - union acpi_ec *ec; - int base; - int alert; -}; - -struct acpi_ec_hc { - acpi_handle handle; - struct acpi_ec_smbus *smbus; -}; - -struct acpi_ec_hc *acpi_get_ec_hc(struct acpi_device *device); diff --git a/drivers/acpi/ibm_acpi.c b/drivers/acpi/ibm_acpi.c deleted file mode 100644 index dc10966..0000000 --- a/drivers/acpi/ibm_acpi.c +++ /dev/null @@ -1,2798 +0,0 @@ -/* - * ibm_acpi.c - IBM ThinkPad ACPI Extras - * - * - * Copyright (C) 2004-2005 Borislav Deianov - * Copyright (C) 2006 Henrique de Moraes Holschuh - * - * 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 - */ - -#define IBM_VERSION "0.13" - -/* - * Changelog: - * - * 2006-11-22 0.13 new maintainer - * changelog now lives in git commit history, and will - * not be updated further in-file. - * - * 2005-08-17 0.12 fix compilation on 2.6.13-rc kernels - * 2005-03-17 0.11 support for 600e, 770x - * thanks to Jamie Lentin - * support for 770e, G41 - * G40 and G41 don't have a thinklight - * temperatures no longer experimental - * experimental brightness control - * experimental volume control - * experimental fan enable/disable - * 2005-01-16 0.10 fix module loading on R30, R31 - * 2005-01-16 0.9 support for 570, R30, R31 - * ultrabay support on A22p, A3x - * limit arg for cmos, led, beep, drop experimental status - * more capable led control on A21e, A22p, T20-22, X20 - * experimental temperatures and fan speed - * experimental embedded controller register dump - * mark more functions as __init, drop incorrect __exit - * use MODULE_VERSION - * thanks to Henrik Brix Andersen - * fix parameter passing on module loading - * thanks to Rusty Russell - * thanks to Jim Radford - * 2004-11-08 0.8 fix init error case, don't return from a macro - * thanks to Chris Wright - * 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20 - * fix led control on A21e - * 2004-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device - * 2004-10-18 0.5 thinklight support on A21e, G40, R32, T20, T21, X20 - * proc file format changed - * video_switch command - * experimental cmos control - * experimental led control - * experimental acpi sounds - * 2004-09-16 0.4 support for module parameters - * hotkey mask can be prefixed by 0x - * video output switching - * video expansion control - * ultrabay eject support - * removed lcd brightness/on/off control, didn't work - * 2004-08-17 0.3 support for R40 - * lcd off, brightness control - * thinklight on/off - * 2004-08-14 0.2 support for T series, X20 - * bluetooth enable/disable - * hotkey events disabled by default - * removed fan control, currently useless - * 2004-08-09 0.1 initial release, support for X series - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#define IBM_NAME "ibm" -#define IBM_DESC "IBM ThinkPad ACPI Extras" -#define IBM_FILE "ibm_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" - -MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); -MODULE_DESCRIPTION(IBM_DESC); -MODULE_VERSION(IBM_VERSION); -MODULE_LICENSE("GPL"); - -#define IBM_DIR IBM_NAME - -#define IBM_LOG IBM_FILE ": " -#define IBM_ERR KERN_ERR IBM_LOG -#define IBM_NOTICE KERN_NOTICE IBM_LOG -#define IBM_INFO KERN_INFO IBM_LOG -#define IBM_DEBUG KERN_DEBUG IBM_LOG - -#define IBM_MAX_ACPI_ARGS 3 - -#define __unused __attribute__ ((unused)) - -static int experimental; -module_param(experimental, int, 0); - -static acpi_handle root_handle = NULL; - -#define IBM_HANDLE(object, parent, paths...) \ - static acpi_handle object##_handle; \ - static acpi_handle *object##_parent = &parent##_handle; \ - static char *object##_path; \ - static char *object##_paths[] = { paths } - -IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ - "\\_SB.PCI.ISA.EC", /* 570 */ - "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */ - "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */ - "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */ - "\\_SB.PCI0.ICH3.EC0", /* R31 */ - "\\_SB.PCI0.LPC.EC", /* all others */ - ); - -IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ - "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ - "\\_SB.PCI0.VID0", /* 770e */ - "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ - "\\_SB.PCI0.AGP.VID", /* all others */ - ); /* R30, R31 */ - -IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ - -IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */ - "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */ - "\\CMS", /* R40, R40e */ - ); /* all others */ -#ifdef CONFIG_ACPI_IBM_DOCK -IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ - "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ - "\\_SB.PCI0.PCI1.DOCK", /* all others */ - "\\_SB.PCI.ISA.SLCE", /* 570 */ - ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ -#endif -#ifdef CONFIG_ACPI_IBM_BAY -IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ - "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */ - "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */ - "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ - ); /* A21e, R30, R31 */ - -IBM_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */ - "_EJ0", /* all others */ - ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */ - -IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */ - "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */ - ); /* all others */ - -IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ - "_EJ0", /* 770x */ - ); /* all others */ -#endif /* CONFIG_ACPI_IBM_BAY */ - -/* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ - -IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ - "^HKEY", /* R30, R31 */ - "HKEY", /* all others */ - ); /* 570 */ - -IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ -IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ - -IBM_HANDLE(led, ec, "SLED", /* 570 */ - "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - "LED", /* all others */ - ); /* R30, R31 */ - -IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ -IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ -IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ -IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ - -IBM_HANDLE(gfan, ec, "GFAN", /* 570 */ - "\\FSPD", /* 600e/x, 770e, 770x */ - ); /* all others */ - -IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ - "JFNS", /* 770x-JL */ - ); /* all others */ - -#define IBM_HKEY_HID "IBM0068" -#define IBM_PCI_HID "PNP0A03" - -enum thermal_access_mode { - IBMACPI_THERMAL_NONE = 0, /* No thermal support */ - IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; -}; - -/* - * FAN ACCESS MODES - * - * IBMACPI_FAN_RD_ACPI_GFAN: - * ACPI GFAN method: returns fan level - * - * see IBMACPI_FAN_WR_ACPI_SFAN - * EC 0x2f not available if GFAN exists - * - * IBMACPI_FAN_WR_ACPI_SFAN: - * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) - * - * EC 0x2f might be available *for reading*, but never for writing. - * - * IBMACPI_FAN_WR_TPEC: - * ThinkPad EC register 0x2f (HFSP): fan control loop mode Supported - * on almost all ThinkPads - * - * Fan speed changes of any sort (including those caused by the - * disengaged mode) are usually done slowly by the firmware as the - * maximum ammount of fan duty cycle change per second seems to be - * limited. - * - * Reading is not available if GFAN exists. - * Writing is not available if SFAN exists. - * - * Bits - * 7 automatic mode engaged; - * (default operation mode of the ThinkPad) - * fan level is ignored in this mode. - * 6 disengage mode (takes precedence over bit 7); - * not available on all thinkpads. May disable - * the tachometer, and speeds up fan to 100% duty-cycle, - * which speeds it up far above the standard RPM - * levels. It is not impossible that it could cause - * hardware damage. - * 5-3 unused in some models. Extra bits for fan level - * in others, but still useless as all values above - * 7 map to the same speed as level 7 in these models. - * 2-0 fan level (0..7 usually) - * 0x00 = stop - * 0x07 = max (set when temperatures critical) - * Some ThinkPads may have other levels, see - * IBMACPI_FAN_WR_ACPI_FANS (X31/X40/X41) - * - * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at - * boot. Apparently the EC does not intialize it, so unless ACPI DSDT - * does so, its initial value is meaningless (0x07). - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * ---- - * - * ThinkPad EC register 0x84 (LSB), 0x85 (MSB): - * Main fan tachometer reading (in RPM) - * - * This register is present on all ThinkPads with a new-style EC, and - * it is known not to be present on the A21m/e, and T22, as there is - * something else in offset 0x84 according to the ACPI DSDT. Other - * ThinkPads from this same time period (and earlier) probably lack the - * tachometer as well. - * - * Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare - * was never fixed by IBM to report the EC firmware version string - * probably support the tachometer (like the early X models), so - * detecting it is quite hard. We need more data to know for sure. - * - * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings - * might result. - * - * FIRMWARE BUG: when EC 0x2f bit 6 is set (disengaged mode), this - * register is not invalidated in ThinkPads that disable tachometer - * readings. Thus, the tachometer readings go stale. - * - * For firmware bugs, refer to: - * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues - * - * IBMACPI_FAN_WR_ACPI_FANS: - * ThinkPad X31, X40, X41. Not available in the X60. - * - * FANS ACPI handle: takes three arguments: low speed, medium speed, - * high speed. ACPI DSDT seems to map these three speeds to levels - * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH - * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3") - * - * The speeds are stored on handles - * (FANA:FAN9), (FANC:FANB), (FANE:FAND). - * - * There are three default speed sets, acessible as handles: - * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H - * - * ACPI DSDT switches which set is in use depending on various - * factors. - * - * IBMACPI_FAN_WR_TPEC is also available and should be used to - * command the fan. The X31/X40/X41 seems to have 8 fan levels, - * but the ACPI tables just mention level 7. - */ - -enum fan_status_access_mode { - IBMACPI_FAN_NONE = 0, /* No fan status or control */ - IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - IBMACPI_FAN_WR_NONE = 0, /* No fan control */ - IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -enum { /* Fan control constants */ - fan_status_offset = 0x2f, /* EC register 0x2f */ - fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) - * 0x84 must be read before 0x85 */ - - IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer - * disengaged */ - IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan - * control */ -}; - -static char *ibm_thinkpad_ec_found = NULL; - -struct ibm_struct { - char *name; - char param[32]; - - char *hid; - struct acpi_driver *driver; - - int (*init) (void); - int (*read) (char *); - int (*write) (char *); - void (*exit) (void); - - void (*notify) (struct ibm_struct *, u32); - acpi_handle *handle; - int type; - struct acpi_device *device; - - int driver_registered; - int proc_created; - int init_called; - int notify_installed; - - int experimental; -}; - -static struct proc_dir_entry *proc_dir = NULL; - -static struct backlight_device *ibm_backlight_device = NULL; - -#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off") -#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") -#define strlencmp(a,b) (strncmp((a), (b), strlen(b))) - -static int acpi_evalf(acpi_handle handle, - void *res, char *method, char *fmt, ...) -{ - char *fmt0 = fmt; - struct acpi_object_list params; - union acpi_object in_objs[IBM_MAX_ACPI_ARGS]; - struct acpi_buffer result, *resultp; - union acpi_object out_obj; - acpi_status status; - va_list ap; - char res_type; - int success; - int quiet; - - if (!*fmt) { - printk(IBM_ERR "acpi_evalf() called with empty format\n"); - return 0; - } - - if (*fmt == 'q') { - quiet = 1; - fmt++; - } else - quiet = 0; - - res_type = *(fmt++); - - params.count = 0; - params.pointer = &in_objs[0]; - - va_start(ap, fmt); - while (*fmt) { - char c = *(fmt++); - switch (c) { - case 'd': /* int */ - in_objs[params.count].integer.value = va_arg(ap, int); - in_objs[params.count++].type = ACPI_TYPE_INTEGER; - break; - /* add more types as needed */ - default: - printk(IBM_ERR "acpi_evalf() called " - "with invalid format character '%c'\n", c); - return 0; - } - } - va_end(ap); - - if (res_type != 'v') { - result.length = sizeof(out_obj); - result.pointer = &out_obj; - resultp = &result; - } else - resultp = NULL; - - status = acpi_evaluate_object(handle, method, ¶ms, resultp); - - switch (res_type) { - case 'd': /* int */ - if (res) - *(int *)res = out_obj.integer.value; - success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; - break; - case 'v': /* void */ - success = status == AE_OK; - break; - /* add more types as needed */ - default: - printk(IBM_ERR "acpi_evalf() called " - "with invalid format character '%c'\n", res_type); - return 0; - } - - if (!success && !quiet) - printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", - method, fmt0, status); - - return success; -} - -static void __unused acpi_print_int(acpi_handle handle, char *method) -{ - int i; - - if (acpi_evalf(handle, &i, method, "d")) - printk(IBM_INFO "%s = 0x%x\n", method, i); - else - printk(IBM_ERR "error calling %s\n", method); -} - -static char *next_cmd(char **cmds) -{ - char *start = *cmds; - char *end; - - while ((end = strchr(start, ',')) && end == start) - start = end + 1; - - if (!end) - return NULL; - - *end = 0; - *cmds = end + 1; - return start; -} - -static int ibm_acpi_driver_init(void) -{ - printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); - printk(IBM_INFO "%s\n", IBM_URL); - - if (ibm_thinkpad_ec_found) - printk(IBM_INFO "ThinkPad EC firmware %s\n", - ibm_thinkpad_ec_found); - - return 0; -} - -static int driver_read(char *p) -{ - int len = 0; - - len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC); - len += sprintf(p + len, "version:\t%s\n", IBM_VERSION); - - return len; -} - -static int hotkey_supported; -static int hotkey_mask_supported; -static int hotkey_orig_status; -static int hotkey_orig_mask; - -static int hotkey_get(int *status, int *mask) -{ - if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) - return 0; - - if (hotkey_mask_supported) - if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) - return 0; - - return 1; -} - -static int hotkey_set(int status, int mask) -{ - int i; - - if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) - return 0; - - if (hotkey_mask_supported) - for (i = 0; i < 32; i++) { - int bit = ((1 << i) & mask) != 0; - if (!acpi_evalf(hkey_handle, - NULL, "MHKM", "vdd", i + 1, bit)) - return 0; - } - - return 1; -} - -static int hotkey_init(void) -{ - /* hotkey not supported on 570 */ - hotkey_supported = hkey_handle != NULL; - - if (hotkey_supported) { - /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, - A30, R30, R31, T20-22, X20-21, X22-24 */ - hotkey_mask_supported = - acpi_evalf(hkey_handle, NULL, "DHKN", "qv"); - - if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask)) - return -ENODEV; - } - - return 0; -} - -static int hotkey_read(char *p) -{ - int status, mask; - int len = 0; - - if (!hotkey_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; - } - - if (!hotkey_get(&status, &mask)) - return -EIO; - - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); - if (hotkey_mask_supported) { - len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); - len += sprintf(p + len, - "commands:\tenable, disable, reset, \n"); - } else { - len += sprintf(p + len, "mask:\t\tnot supported\n"); - len += sprintf(p + len, "commands:\tenable, disable, reset\n"); - } - - return len; -} - -static int hotkey_write(char *buf) -{ - int status, mask; - char *cmd; - int do_cmd = 0; - - if (!hotkey_supported) - return -ENODEV; - - if (!hotkey_get(&status, &mask)) - return -EIO; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status = 1; - } else if (strlencmp(cmd, "disable") == 0) { - status = 0; - } else if (strlencmp(cmd, "reset") == 0) { - status = hotkey_orig_status; - mask = hotkey_orig_mask; - } else if (sscanf(cmd, "0x%x", &mask) == 1) { - /* mask set */ - } else if (sscanf(cmd, "%x", &mask) == 1) { - /* mask set */ - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !hotkey_set(status, mask)) - return -EIO; - - return 0; -} - -static void hotkey_exit(void) -{ - if (hotkey_supported) - hotkey_set(hotkey_orig_status, hotkey_orig_mask); -} - -static void hotkey_notify(struct ibm_struct *ibm, u32 event) -{ - int hkey; - - if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) - acpi_bus_generate_event(ibm->device, event, hkey); - else { - printk(IBM_ERR "unknown hotkey event %d\n", event); - acpi_bus_generate_event(ibm->device, event, 0); - } -} - -static int bluetooth_supported; - -static int bluetooth_init(void) -{ - /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, - G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ - bluetooth_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); - - return 0; -} - -static int bluetooth_status(void) -{ - int status; - - if (!bluetooth_supported || - !acpi_evalf(hkey_handle, &status, "GBDC", "d")) - status = 0; - - return status; -} - -static int bluetooth_read(char *p) -{ - int len = 0; - int status = bluetooth_status(); - - if (!bluetooth_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int bluetooth_write(char *buf) -{ - int status = bluetooth_status(); - char *cmd; - int do_cmd = 0; - - if (!bluetooth_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) - return -EIO; - - return 0; -} - -static int wan_supported; - -static int wan_init(void) -{ - wan_supported = hkey_handle && - acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); - - return 0; -} - -static int wan_status(void) -{ - int status; - - if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) - status = 0; - - return status; -} - -static int wan_read(char *p) -{ - int len = 0; - int status = wan_status(); - - if (!wan_supported) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!(status & 1)) - len += sprintf(p + len, "status:\t\tnot installed\n"); - else { - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); - len += sprintf(p + len, "commands:\tenable, disable\n"); - } - - return len; -} - -static int wan_write(char *buf) -{ - int status = wan_status(); - char *cmd; - int do_cmd = 0; - - if (!wan_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "enable") == 0) { - status |= 2; - } else if (strlencmp(cmd, "disable") == 0) { - status &= ~2; - } else - return -EINVAL; - do_cmd = 1; - } - - if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; - - return 0; -} - -enum video_access_mode { - IBMACPI_VIDEO_NONE = 0, - IBMACPI_VIDEO_570, /* 570 */ - IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - IBMACPI_VIDEO_NEW, /* all others */ -}; - -static enum video_access_mode video_supported; -static int video_orig_autosw; - -static int video_init(void) -{ - int ivga; - - if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) - /* G41, assume IVGA doesn't change */ - vid_handle = vid2_handle; - - if (!vid_handle) - /* video switching not supported on R30, R31 */ - video_supported = IBMACPI_VIDEO_NONE; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) - /* 570 */ - video_supported = IBMACPI_VIDEO_570; - else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) - /* 600e/x, 770e, 770x */ - video_supported = IBMACPI_VIDEO_770; - else - /* all others */ - video_supported = IBMACPI_VIDEO_NEW; - - return 0; -} - -static int video_status(void) -{ - int status = 0; - int i; - - if (video_supported == IBMACPI_VIDEO_570) { - if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) - status = i & 3; - } else if (video_supported == IBMACPI_VIDEO_770) { - if (acpi_evalf(NULL, &i, "\\VCDL", "d")) - status |= 0x01 * i; - if (acpi_evalf(NULL, &i, "\\VCDC", "d")) - status |= 0x02 * i; - } else if (video_supported == IBMACPI_VIDEO_NEW) { - acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); - if (acpi_evalf(NULL, &i, "\\VCDC", "d")) - status |= 0x02 * i; - - acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0); - if (acpi_evalf(NULL, &i, "\\VCDL", "d")) - status |= 0x01 * i; - if (acpi_evalf(NULL, &i, "\\VCDD", "d")) - status |= 0x08 * i; - } - - return status; -} - -static int video_autosw(void) -{ - int autosw = 0; - - if (video_supported == IBMACPI_VIDEO_570) - acpi_evalf(vid_handle, &autosw, "SWIT", "d"); - else if (video_supported == IBMACPI_VIDEO_770 || - video_supported == IBMACPI_VIDEO_NEW) - acpi_evalf(vid_handle, &autosw, "^VDEE", "d"); - - return autosw & 1; -} - -static int video_read(char *p) -{ - int status = video_status(); - int autosw = video_autosw(); - int len = 0; - - if (!video_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; - } - - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); - len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); - if (video_supported == IBMACPI_VIDEO_NEW) - len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); - len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); - len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); - len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); - if (video_supported == IBMACPI_VIDEO_NEW) - len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); - len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); - len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); - - return len; -} - -static int video_switch(void) -{ - int autosw = video_autosw(); - int ret; - - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - ret = video_supported == IBMACPI_VIDEO_570 ? - acpi_evalf(ec_handle, NULL, "_Q16", "v") : - acpi_evalf(vid_handle, NULL, "VSWT", "v"); - acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); - - return ret; -} - -static int video_expand(void) -{ - if (video_supported == IBMACPI_VIDEO_570) - return acpi_evalf(ec_handle, NULL, "_Q17", "v"); - else if (video_supported == IBMACPI_VIDEO_770) - return acpi_evalf(vid_handle, NULL, "VEXP", "v"); - else - return acpi_evalf(NULL, NULL, "\\VEXP", "v"); -} - -static int video_switch2(int status) -{ - int ret; - - if (video_supported == IBMACPI_VIDEO_570) { - ret = acpi_evalf(NULL, NULL, - "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); - } else if (video_supported == IBMACPI_VIDEO_770) { - int autosw = video_autosw(); - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - - ret = acpi_evalf(vid_handle, NULL, - "ASWT", "vdd", status * 0x100, 0); - - acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); - } else { - ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && - acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); - } - - return ret; -} - -static int video_write(char *buf) -{ - char *cmd; - int enable, disable, status; - - if (!video_supported) - return -ENODEV; - - enable = disable = 0; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "lcd_enable") == 0) { - enable |= 0x01; - } else if (strlencmp(cmd, "lcd_disable") == 0) { - disable |= 0x01; - } else if (strlencmp(cmd, "crt_enable") == 0) { - enable |= 0x02; - } else if (strlencmp(cmd, "crt_disable") == 0) { - disable |= 0x02; - } else if (video_supported == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_enable") == 0) { - enable |= 0x08; - } else if (video_supported == IBMACPI_VIDEO_NEW && - strlencmp(cmd, "dvi_disable") == 0) { - disable |= 0x08; - } else if (strlencmp(cmd, "auto_enable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) - return -EIO; - } else if (strlencmp(cmd, "auto_disable") == 0) { - if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) - return -EIO; - } else if (strlencmp(cmd, "video_switch") == 0) { - if (!video_switch()) - return -EIO; - } else if (strlencmp(cmd, "expand_toggle") == 0) { - if (!video_expand()) - return -EIO; - } else - return -EINVAL; - } - - if (enable || disable) { - status = (video_status() & 0x0f & ~disable) | enable; - if (!video_switch2(status)) - return -EIO; - } - - return 0; -} - -static void video_exit(void) -{ - acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); -} - -static int light_supported; -static int light_status_supported; - -static int light_init(void) -{ - /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ - light_supported = (cmos_handle || lght_handle) && !ledb_handle; - - if (light_supported) - /* light status not supported on - 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */ - light_status_supported = acpi_evalf(ec_handle, NULL, - "KBLT", "qv"); - - return 0; -} - -static int light_read(char *p) -{ - int len = 0; - int status = 0; - - if (!light_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - } else if (!light_status_supported) { - len += sprintf(p + len, "status:\t\tunknown\n"); - len += sprintf(p + len, "commands:\ton, off\n"); - } else { - if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) - return -EIO; - len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); - len += sprintf(p + len, "commands:\ton, off\n"); - } - - return len; -} - -static int light_write(char *buf) -{ - int cmos_cmd, lght_cmd; - char *cmd; - int success; - - if (!light_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "on") == 0) { - cmos_cmd = 0x0c; - lght_cmd = 1; - } else if (strlencmp(cmd, "off") == 0) { - cmos_cmd = 0x0d; - lght_cmd = 0; - } else - return -EINVAL; - - success = cmos_handle ? - acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : - acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); - if (!success) - return -EIO; - } - - return 0; -} - -#if defined(CONFIG_ACPI_IBM_DOCK) || defined(CONFIG_ACPI_IBM_BAY) -static int _sta(acpi_handle handle) -{ - int status; - - if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) - status = 0; - - return status; -} -#endif - -#ifdef CONFIG_ACPI_IBM_DOCK -#define dock_docked() (_sta(dock_handle) & 1) - -static int dock_read(char *p) -{ - int len = 0; - int docked = dock_docked(); - - if (!dock_handle) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else if (!docked) - len += sprintf(p + len, "status:\t\tundocked\n"); - else { - len += sprintf(p + len, "status:\t\tdocked\n"); - len += sprintf(p + len, "commands:\tdock, undock\n"); - } - - return len; -} - -static int dock_write(char *buf) -{ - char *cmd; - - if (!dock_docked()) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (strlencmp(cmd, "undock") == 0) { - if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) || - !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1)) - return -EIO; - } else if (strlencmp(cmd, "dock") == 0) { - if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static void dock_notify(struct ibm_struct *ibm, u32 event) -{ - int docked = dock_docked(); - int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID); - - if (event == 1 && !pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 1); /* button */ - else if (event == 1 && pci) /* 570 */ - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ - else if (event == 3 && docked) - acpi_bus_generate_event(ibm->device, event, 1); /* button */ - else if (event == 3 && !docked) - acpi_bus_generate_event(ibm->device, event, 2); /* undock */ - else if (event == 0 && docked) - acpi_bus_generate_event(ibm->device, event, 3); /* dock */ - else { - printk(IBM_ERR "unknown dock event %d, status %d\n", - event, _sta(dock_handle)); - acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ - } -} -#endif - -#ifdef CONFIG_ACPI_IBM_BAY -static int bay_status_supported; -static int bay_status2_supported; -static int bay_eject_supported; -static int bay_eject2_supported; - -static int bay_init(void) -{ - bay_status_supported = bay_handle && - acpi_evalf(bay_handle, NULL, "_STA", "qv"); - bay_status2_supported = bay2_handle && - acpi_evalf(bay2_handle, NULL, "_STA", "qv"); - - bay_eject_supported = bay_handle && bay_ej_handle && - (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental); - bay_eject2_supported = bay2_handle && bay2_ej_handle && - (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental); - - return 0; -} - -#define bay_occupied(b) (_sta(b##_handle) & 1) - -static int bay_read(char *p) -{ - int len = 0; - int occupied = bay_occupied(bay); - int occupied2 = bay_occupied(bay2); - int eject, eject2; - - len += sprintf(p + len, "status:\t\t%s\n", bay_status_supported ? - (occupied ? "occupied" : "unoccupied") : - "not supported"); - if (bay_status2_supported) - len += sprintf(p + len, "status2:\t%s\n", occupied2 ? - "occupied" : "unoccupied"); - - eject = bay_eject_supported && occupied; - eject2 = bay_eject2_supported && occupied2; - - if (eject && eject2) - len += sprintf(p + len, "commands:\teject, eject2\n"); - else if (eject) - len += sprintf(p + len, "commands:\teject\n"); - else if (eject2) - len += sprintf(p + len, "commands:\teject2\n"); - - return len; -} - -static int bay_write(char *buf) -{ - char *cmd; - - if (!bay_eject_supported && !bay_eject2_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (bay_eject_supported && strlencmp(cmd, "eject") == 0) { - if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) - return -EIO; - } else if (bay_eject2_supported && - strlencmp(cmd, "eject2") == 0) { - if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static void bay_notify(struct ibm_struct *ibm, u32 event) -{ - acpi_bus_generate_event(ibm->device, event, 0); -} -#endif /* CONFIG_ACPI_IBM_BAY */ - -static int cmos_read(char *p) -{ - int len = 0; - - /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, - R30, R31, T20-22, X20-21 */ - if (!cmos_handle) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else { - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "commands:\t ( is 0-21)\n"); - } - - return len; -} - -static int cmos_eval(int cmos_cmd) -{ - if (cmos_handle) - return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd); - else - return 1; -} - -static int cmos_write(char *buf) -{ - char *cmd; - int cmos_cmd; - - if (!cmos_handle) - return -EINVAL; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%u", &cmos_cmd) == 1 && - cmos_cmd >= 0 && cmos_cmd <= 21) { - /* cmos_cmd set */ - } else - return -EINVAL; - - if (!cmos_eval(cmos_cmd)) - return -EIO; - } - - return 0; -} - -enum led_access_mode { - IBMACPI_LED_NONE = 0, - IBMACPI_LED_570, /* 570 */ - IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - IBMACPI_LED_NEW, /* all others */ -}; -static enum led_access_mode led_supported; - -static int led_init(void) -{ - if (!led_handle) - /* led not supported on R30, R31 */ - led_supported = IBMACPI_LED_NONE; - else if (strlencmp(led_path, "SLED") == 0) - /* 570 */ - led_supported = IBMACPI_LED_570; - else if (strlencmp(led_path, "SYSL") == 0) - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - led_supported = IBMACPI_LED_OLD; - else - /* all others */ - led_supported = IBMACPI_LED_NEW; - - return 0; -} - -#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) - -static int led_read(char *p) -{ - int len = 0; - - if (!led_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; - } - len += sprintf(p + len, "status:\t\tsupported\n"); - - if (led_supported == IBMACPI_LED_570) { - /* 570 */ - int i, status; - for (i = 0; i < 8; i++) { - if (!acpi_evalf(ec_handle, - &status, "GLED", "dd", 1 << i)) - return -EIO; - len += sprintf(p + len, "%d:\t\t%s\n", - i, led_status(status)); - } - } - - len += sprintf(p + len, "commands:\t" - " on, off, blink ( is 0-7)\n"); - - return len; -} - -/* off, on, blink */ -static const int led_sled_arg1[] = { 0, 1, 3 }; -static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ -static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ -static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; - -#define EC_HLCL 0x0c -#define EC_HLBL 0x0d -#define EC_HLMS 0x0e - -static int led_write(char *buf) -{ - char *cmd; - int led, ind, ret; - - if (!led_supported) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) - return -EINVAL; - - if (strstr(cmd, "off")) { - ind = 0; - } else if (strstr(cmd, "on")) { - ind = 1; - } else if (strstr(cmd, "blink")) { - ind = 2; - } else - return -EINVAL; - - if (led_supported == IBMACPI_LED_570) { - /* 570 */ - led = 1 << led; - if (!acpi_evalf(led_handle, NULL, NULL, "vdd", - led, led_sled_arg1[ind])) - return -EIO; - } else if (led_supported == IBMACPI_LED_OLD) { - /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ - led = 1 << led; - ret = ec_write(EC_HLMS, led); - if (ret >= 0) - ret = - ec_write(EC_HLBL, led * led_exp_hlbl[ind]); - if (ret >= 0) - ret = - ec_write(EC_HLCL, led * led_exp_hlcl[ind]); - if (ret < 0) - return ret; - } else { - /* all others */ - if (!acpi_evalf(led_handle, NULL, NULL, "vdd", - led, led_led_arg1[ind])) - return -EIO; - } - } - - return 0; -} - -static int beep_read(char *p) -{ - int len = 0; - - if (!beep_handle) - len += sprintf(p + len, "status:\t\tnot supported\n"); - else { - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "commands:\t ( is 0-17)\n"); - } - - return len; -} - -static int beep_write(char *buf) -{ - char *cmd; - int beep_cmd; - - if (!beep_handle) - return -ENODEV; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "%u", &beep_cmd) == 1 && - beep_cmd >= 0 && beep_cmd <= 17) { - /* beep_cmd set */ - } else - return -EINVAL; - if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) - return -EIO; - } - - return 0; -} - -static int acpi_ec_read(int i, u8 * p) -{ - int v; - - if (ecrd_handle) { - if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i)) - return 0; - *p = v; - } else { - if (ec_read(i, p) < 0) - return 0; - } - - return 1; -} - -static int acpi_ec_write(int i, u8 v) -{ - if (ecwr_handle) { - if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v)) - return 0; - } else { - if (ec_write(i, v) < 0) - return 0; - } - - return 1; -} - -static enum thermal_access_mode thermal_read_mode; - -static int thermal_init(void) -{ - u8 t, ta1, ta2; - int i; - int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); - - if (ibm_thinkpad_ec_found && experimental) { - /* - * Direct EC access mode: sensors at registers - * 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for - * non-implemented, thermal sensors return 0x80 when - * not available - */ - - ta1 = ta2 = 0; - for (i = 0; i < 8; i++) { - if (likely(acpi_ec_read(0x78 + i, &t))) { - ta1 |= t; - } else { - ta1 = 0; - break; - } - if (likely(acpi_ec_read(0xC0 + i, &t))) { - ta2 |= t; - } else { - ta1 = 0; - break; - } - } - if (ta1 == 0) { - /* This is sheer paranoia, but we handle it anyway */ - if (acpi_tmp7) { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "falling back to ACPI TMPx access mode\n"); - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "disabling thermal sensors access\n"); - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - } else { - thermal_read_mode = - (ta2 != 0) ? - IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; - } - } else if (acpi_tmp7) { - if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { - /* 600e/x, 770e, 770x */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; - } else { - /* Standard ACPI TMPx access, max 8 sensors */ - thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; - } - } else { - /* temperatures not supported on 570, G4x, R30, R31, R32 */ - thermal_read_mode = IBMACPI_THERMAL_NONE; - } - - return 0; -} - -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) -{ - int i, t; - s8 tmp; - char tmpi[] = "TMPi"; - - if (!s) - return -EINVAL; - - switch (thermal_read_mode) { -#if IBMACPI_MAX_THERMAL_SENSORS >= 16 - case IBMACPI_THERMAL_TPEC_16: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0xC0 + i, &tmp)) - return -EIO; - s->temp[i + 8] = tmp * 1000; - } - /* fallthrough */ -#endif - case IBMACPI_THERMAL_TPEC_8: - for (i = 0; i < 8; i++) { - if (!acpi_ec_read(0x78 + i, &tmp)) - return -EIO; - s->temp[i] = tmp * 1000; - } - return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; - - case IBMACPI_THERMAL_ACPI_UPDT: - if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) - return -EIO; - for (i = 0; i < 8; i++) { - tmpi[3] = '0' + i; - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - s->temp[i] = (t - 2732) * 100; - } - return 8; - - case IBMACPI_THERMAL_ACPI_TMP07: - for (i = 0; i < 8; i++) { - tmpi[3] = '0' + i; - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - s->temp[i] = t * 1000; - } - return 8; - - case IBMACPI_THERMAL_NONE: - default: - return 0; - } -} - -static int thermal_read(char *p) -{ - int len = 0; - int n, i; - struct ibm_thermal_sensors_struct t; - - n = thermal_get_sensors(&t); - if (unlikely(n < 0)) - return n; - - len += sprintf(p + len, "temperatures:\t"); - - if (n > 0) { - for (i = 0; i < (n - 1); i++) - len += sprintf(p + len, "%d ", t.temp[i] / 1000); - len += sprintf(p + len, "%d\n", t.temp[i] / 1000); - } else - len += sprintf(p + len, "not supported\n"); - - return len; -} - -static u8 ecdump_regs[256]; - -static int ecdump_read(char *p) -{ - int len = 0; - int i, j; - u8 v; - - len += sprintf(p + len, "EC " - " +00 +01 +02 +03 +04 +05 +06 +07" - " +08 +09 +0a +0b +0c +0d +0e +0f\n"); - for (i = 0; i < 256; i += 16) { - len += sprintf(p + len, "EC 0x%02x:", i); - for (j = 0; j < 16; j++) { - if (!acpi_ec_read(i + j, &v)) - break; - if (v != ecdump_regs[i + j]) - len += sprintf(p + len, " *%02x", v); - else - len += sprintf(p + len, " %02x", v); - ecdump_regs[i + j] = v; - } - len += sprintf(p + len, "\n"); - if (j != 16) - break; - } - - /* These are way too dangerous to advertise openly... */ -#if 0 - len += sprintf(p + len, "commands:\t0x 0x" - " ( is 00-ff, is 00-ff)\n"); - len += sprintf(p + len, "commands:\t0x " - " ( is 00-ff, is 0-255)\n"); -#endif - return len; -} - -static int ecdump_write(char *buf) -{ - char *cmd; - int i, v; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) { - /* i and v set */ - } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) { - /* i and v set */ - } else - return -EINVAL; - if (i >= 0 && i < 256 && v >= 0 && v < 256) { - if (!acpi_ec_write(i, v)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static int brightness_offset = 0x31; - -static int brightness_get(struct backlight_device *bd) -{ - u8 level; - if (!acpi_ec_read(brightness_offset, &level)) - return -EIO; - - level &= 0x7; - - return level; -} - -static int brightness_read(char *p) -{ - int len = 0; - int level; - - if ((level = brightness_get(NULL)) < 0) { - len += sprintf(p + len, "level:\t\tunreadable\n"); - } else { - len += sprintf(p + len, "level:\t\t%d\n", level & 0x7); - len += sprintf(p + len, "commands:\tup, down\n"); - len += sprintf(p + len, "commands:\tlevel " - " ( is 0-7)\n"); - } - - return len; -} - -#define BRIGHTNESS_UP 4 -#define BRIGHTNESS_DOWN 5 - -static int brightness_set(int value) -{ - int cmos_cmd, inc, i; - int current_value = brightness_get(NULL); - - value &= 7; - - cmos_cmd = value > current_value ? BRIGHTNESS_UP : BRIGHTNESS_DOWN; - inc = value > current_value ? 1 : -1; - for (i = current_value; i != value; i += inc) { - if (!cmos_eval(cmos_cmd)) - return -EIO; - if (!acpi_ec_write(brightness_offset, i + inc)) - return -EIO; - } - - return 0; -} - -static int brightness_write(char *buf) -{ - int level; - int new_level; - char *cmd; - - while ((cmd = next_cmd(&buf))) { - if ((level = brightness_get(NULL)) < 0) - return level; - level &= 7; - - if (strlencmp(cmd, "up") == 0) { - new_level = level == 7 ? 7 : level + 1; - } else if (strlencmp(cmd, "down") == 0) { - new_level = level == 0 ? 0 : level - 1; - } else if (sscanf(cmd, "level %d", &new_level) == 1 && - new_level >= 0 && new_level <= 7) { - /* new_level set */ - } else - return -EINVAL; - - brightness_set(new_level); - } - - return 0; -} - -static int brightness_update_status(struct backlight_device *bd) -{ - return brightness_set( - (bd->props.fb_blank == FB_BLANK_UNBLANK && - bd->props.power == FB_BLANK_UNBLANK) ? - bd->props.brightness : 0); -} - -static struct backlight_ops ibm_backlight_data = { - .get_brightness = brightness_get, - .update_status = brightness_update_status, -}; - -static int brightness_init(void) -{ - int b; - - b = brightness_get(NULL); - if (b < 0) - return b; - - ibm_backlight_device = backlight_device_register("ibm", NULL, NULL, - &ibm_backlight_data); - if (IS_ERR(ibm_backlight_device)) { - printk(IBM_ERR "Could not register backlight device\n"); - return PTR_ERR(ibm_backlight_device); - } - - ibm_backlight_device->props.max_brightness = 7; - ibm_backlight_device->props.brightness = b; - backlight_update_status(ibm_backlight_device); - - return 0; -} - -static void brightness_exit(void) -{ - if (ibm_backlight_device) { - backlight_device_unregister(ibm_backlight_device); - ibm_backlight_device = NULL; - } -} - -static int volume_offset = 0x30; - -static int volume_read(char *p) -{ - int len = 0; - u8 level; - - if (!acpi_ec_read(volume_offset, &level)) { - len += sprintf(p + len, "level:\t\tunreadable\n"); - } else { - len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); - len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); - len += sprintf(p + len, "commands:\tup, down, mute\n"); - len += sprintf(p + len, "commands:\tlevel " - " ( is 0-15)\n"); - } - - return len; -} - -#define VOLUME_DOWN 0 -#define VOLUME_UP 1 -#define VOLUME_MUTE 2 - -static int volume_write(char *buf) -{ - int cmos_cmd, inc, i; - u8 level, mute; - int new_level, new_mute; - char *cmd; - - while ((cmd = next_cmd(&buf))) { - if (!acpi_ec_read(volume_offset, &level)) - return -EIO; - new_mute = mute = level & 0x40; - new_level = level = level & 0xf; - - if (strlencmp(cmd, "up") == 0) { - if (mute) - new_mute = 0; - else - new_level = level == 15 ? 15 : level + 1; - } else if (strlencmp(cmd, "down") == 0) { - if (mute) - new_mute = 0; - else - new_level = level == 0 ? 0 : level - 1; - } else if (sscanf(cmd, "level %d", &new_level) == 1 && - new_level >= 0 && new_level <= 15) { - /* new_level set */ - } else if (strlencmp(cmd, "mute") == 0) { - new_mute = 0x40; - } else - return -EINVAL; - - if (new_level != level) { /* mute doesn't change */ - cmos_cmd = new_level > level ? VOLUME_UP : VOLUME_DOWN; - inc = new_level > level ? 1 : -1; - - if (mute && (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, level))) - return -EIO; - - for (i = level; i != new_level; i += inc) - if (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, i + inc)) - return -EIO; - - if (mute && (!cmos_eval(VOLUME_MUTE) || - !acpi_ec_write(volume_offset, - new_level + mute))) - return -EIO; - } - - if (new_mute != mute) { /* level doesn't change */ - cmos_cmd = new_mute ? VOLUME_MUTE : VOLUME_UP; - - if (!cmos_eval(cmos_cmd) || - !acpi_ec_write(volume_offset, level + new_mute)) - return -EIO; - } - } - - return 0; -} - -static enum fan_status_access_mode fan_status_access_mode; -static enum fan_control_access_mode fan_control_access_mode; -static enum fan_control_commands fan_control_commands; - -static int fan_control_status_known; -static u8 fan_control_initial_status; - -static void fan_watchdog_fire(struct work_struct *ignored); -static int fan_watchdog_maxinterval; -static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); - -static int fan_init(void) -{ - fan_status_access_mode = IBMACPI_FAN_NONE; - fan_control_access_mode = IBMACPI_FAN_WR_NONE; - fan_control_commands = 0; - fan_control_status_known = 1; - fan_watchdog_maxinterval = 0; - - if (gfan_handle) { - /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; - } else { - /* all other ThinkPads: note that even old-style - * ThinkPad ECs supports the fan control register */ - if (likely(acpi_ec_read(fan_status_offset, - &fan_control_initial_status))) { - fan_status_access_mode = IBMACPI_FAN_RD_TPEC; - - /* In some ThinkPads, neither the EC nor the ACPI - * DSDT initialize the fan status, and it ends up - * being set to 0x07 when it *could* be either - * 0x07 or 0x80. - * - * Enable for TP-1Y (T43), TP-78 (R51e), - * TP-76 (R52), TP-70 (T43, R52), which are known - * to be buggy. */ - if (fan_control_initial_status == 0x07 && - ibm_thinkpad_ec_found && - ((ibm_thinkpad_ec_found[0] == '1' && - ibm_thinkpad_ec_found[1] == 'Y') || - (ibm_thinkpad_ec_found[0] == '7' && - (ibm_thinkpad_ec_found[1] == '6' || - ibm_thinkpad_ec_found[1] == '8' || - ibm_thinkpad_ec_found[1] == '0')) - )) { - printk(IBM_NOTICE - "fan_init: initial fan status is " - "unknown, assuming it is in auto " - "mode\n"); - fan_control_status_known = 0; - } - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "fan status and control unavailable\n"); - return 0; - } - } - - if (sfan_handle) { - /* 570, 770x-JL */ - fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | IBMACPI_FAN_CMD_ENABLE; - } else { - if (!gfan_handle) { - /* gfan without sfan means no fan control */ - /* all other models implement TP EC 0x2f control */ - - if (fans_handle) { - /* X31, X40, X41 */ - fan_control_access_mode = - IBMACPI_FAN_WR_ACPI_FANS; - fan_control_commands |= - IBMACPI_FAN_CMD_SPEED | - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } else { - fan_control_access_mode = IBMACPI_FAN_WR_TPEC; - fan_control_commands |= - IBMACPI_FAN_CMD_LEVEL | - IBMACPI_FAN_CMD_ENABLE; - } - } - } - - return 0; -} - -static int fan_get_status(u8 *status) -{ - u8 s; - - /* TODO: - * Add IBMACPI_FAN_RD_ACPI_FANS ? */ - - switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_ACPI_GFAN: - /* 570, 600e/x, 770e, 770x */ - - if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) - return -EIO; - - if (likely(status)) - *status = s & 0x07; - - break; - - case IBMACPI_FAN_RD_TPEC: - /* all except 570, 600e/x, 770e, 770x */ - if (unlikely(!acpi_ec_read(fan_status_offset, &s))) - return -EIO; - - if (likely(status)) - *status = s; - - break; - - default: - return -ENXIO; - } - - return 0; -} - -static int fan_get_speed(unsigned int *speed) -{ - u8 hi, lo; - - switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_TPEC: - /* all except 570, 600e/x, 770e, 770x */ - if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) || - !acpi_ec_read(fan_rpm_offset + 1, &hi))) - return -EIO; - - if (likely(speed)) - *speed = (hi << 8) | lo; - - break; - - default: - return -ENXIO; - } - - return 0; -} - -static void fan_exit(void) -{ - cancel_delayed_work(&fan_watchdog_task); - flush_scheduled_work(); -} - -static void fan_watchdog_reset(void) -{ - static int fan_watchdog_active = 0; - - if (fan_watchdog_active) - cancel_delayed_work(&fan_watchdog_task); - - if (fan_watchdog_maxinterval > 0) { - fan_watchdog_active = 1; - if (!schedule_delayed_work(&fan_watchdog_task, - msecs_to_jiffies(fan_watchdog_maxinterval - * 1000))) { - printk(IBM_ERR "failed to schedule the fan watchdog, " - "watchdog will not trigger\n"); - } - } else - fan_watchdog_active = 0; -} - -static int fan_read(char *p) -{ - int len = 0; - int rc; - u8 status; - unsigned int speed = 0; - - switch (fan_status_access_mode) { - case IBMACPI_FAN_RD_ACPI_GFAN: - /* 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) - return rc; - - len += sprintf(p + len, "status:\t\t%s\n" - "level:\t\t%d\n", - (status != 0) ? "enabled" : "disabled", status); - break; - - case IBMACPI_FAN_RD_TPEC: - /* all except 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status(&status)) < 0) - return rc; - - if (unlikely(!fan_control_status_known)) { - if (status != fan_control_initial_status) - fan_control_status_known = 1; - else - /* Return most likely status. In fact, it - * might be the only possible status */ - status = IBMACPI_FAN_EC_AUTO; - } - - len += sprintf(p + len, "status:\t\t%s\n", - (status != 0) ? "enabled" : "disabled"); - - /* No ThinkPad boots on disengaged mode, we can safely - * assume the tachometer is online if fan control status - * was unknown */ - if ((rc = fan_get_speed(&speed)) < 0) - return rc; - - len += sprintf(p + len, "speed:\t\t%d\n", speed); - - if (status & IBMACPI_FAN_EC_DISENGAGED) - /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); - else if (status & IBMACPI_FAN_EC_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); - else - len += sprintf(p + len, "level:\t\t%d\n", status); - break; - - case IBMACPI_FAN_NONE: - default: - len += sprintf(p + len, "status:\t\tnot supported\n"); - } - - if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel "); - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " ( is 0-7)\n"); - break; - - default: - len += sprintf(p + len, " ( is 0-7, " - "auto, disengaged)\n"); - break; - } - } - - if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) - len += sprintf(p + len, "commands:\tenable, disable\n" - "commands:\twatchdog ( is 0 (off), " - "1-120 (seconds))\n"); - - if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed " - " ( is 0-65535)\n"); - - return len; -} - -static int fan_set_level(int level) -{ - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_SFAN: - if (level >= 0 && level <= 7) { - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) - return -EIO; - } else - return -EINVAL; - break; - - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((level != IBMACPI_FAN_EC_AUTO) && - (level != IBMACPI_FAN_EC_DISENGAGED) && - ((level < 0) || (level > 7))) - return -EINVAL; - - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - else - fan_control_status_known = 1; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_enable(void) -{ - u8 s; - int rc; - - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - /* Don't go out of emergency fan mode */ - if (s != 7) - s = IBMACPI_FAN_EC_AUTO; - - if (!acpi_ec_write(fan_status_offset, s)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_FAN_WR_ACPI_SFAN: - if ((rc = fan_get_status(&s)) < 0) - return rc; - - s &= 0x07; - - /* Set fan to at least level 4 */ - if (s < 4) - s = 4; - - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) - return -EIO; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_disable(void) -{ - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - case IBMACPI_FAN_WR_TPEC: - if (!acpi_ec_write(fan_status_offset, 0x00)) - return -EIO; - else - fan_control_status_known = 1; - break; - - case IBMACPI_FAN_WR_ACPI_SFAN: - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) - return -EIO; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_speed(int speed) -{ - switch (fan_control_access_mode) { - case IBMACPI_FAN_WR_ACPI_FANS: - if (speed >= 0 && speed <= 65535) { - if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", - speed, speed, speed)) - return -EIO; - } else - return -EINVAL; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_write_cmd_level(const char *cmd, int *rc) -{ - int level; - - if (strlencmp(cmd, "level auto") == 0) - level = IBMACPI_FAN_EC_AUTO; - else if (strlencmp(cmd, "level disengaged") == 0) - level = IBMACPI_FAN_EC_DISENGAGED; - else if (sscanf(cmd, "level %d", &level) != 1) - return 0; - - if ((*rc = fan_set_level(level)) == -ENXIO) - printk(IBM_ERR "level command accepted for unsupported " - "access mode %d", fan_control_access_mode); - - return 1; -} - -static int fan_write_cmd_enable(const char *cmd, int *rc) -{ - if (strlencmp(cmd, "enable") != 0) - return 0; - - if ((*rc = fan_set_enable()) == -ENXIO) - printk(IBM_ERR "enable command accepted for unsupported " - "access mode %d", fan_control_access_mode); - - return 1; -} - -static int fan_write_cmd_disable(const char *cmd, int *rc) -{ - if (strlencmp(cmd, "disable") != 0) - return 0; - - if ((*rc = fan_set_disable()) == -ENXIO) - printk(IBM_ERR "disable command accepted for unsupported " - "access mode %d", fan_control_access_mode); - - return 1; -} - -static int fan_write_cmd_speed(const char *cmd, int *rc) -{ - int speed; - - /* TODO: - * Support speed ? */ - - if (sscanf(cmd, "speed %d", &speed) != 1) - return 0; - - if ((*rc = fan_set_speed(speed)) == -ENXIO) - printk(IBM_ERR "speed command accepted for unsupported " - "access mode %d", fan_control_access_mode); - - return 1; -} - -static int fan_write_cmd_watchdog(const char *cmd, int *rc) -{ - int interval; - - if (sscanf(cmd, "watchdog %d", &interval) != 1) - return 0; - - if (interval < 0 || interval > 120) - *rc = -EINVAL; - else - fan_watchdog_maxinterval = interval; - - return 1; -} - -static int fan_write(char *buf) -{ - char *cmd; - int rc = 0; - - while (!rc && (cmd = next_cmd(&buf))) { - if (!((fan_control_commands & IBMACPI_FAN_CMD_LEVEL) && - fan_write_cmd_level(cmd, &rc)) && - !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && - (fan_write_cmd_enable(cmd, &rc) || - fan_write_cmd_disable(cmd, &rc) || - fan_write_cmd_watchdog(cmd, &rc))) && - !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && - fan_write_cmd_speed(cmd, &rc)) - ) - rc = -EINVAL; - else if (!rc) - fan_watchdog_reset(); - } - - return rc; -} - -static void fan_watchdog_fire(struct work_struct *ignored) -{ - printk(IBM_NOTICE "fan watchdog: enabling fan\n"); - if (fan_set_enable()) { - printk(IBM_ERR "fan watchdog: error while enabling fan\n"); - /* reschedule for later */ - fan_watchdog_reset(); - } -} - -static struct ibm_struct ibms[] = { - { - .name = "driver", - .init = ibm_acpi_driver_init, - .read = driver_read, - }, - { - .name = "hotkey", - .hid = IBM_HKEY_HID, - .init = hotkey_init, - .read = hotkey_read, - .write = hotkey_write, - .exit = hotkey_exit, - .notify = hotkey_notify, - .handle = &hkey_handle, - .type = ACPI_DEVICE_NOTIFY, - }, - { - .name = "bluetooth", - .init = bluetooth_init, - .read = bluetooth_read, - .write = bluetooth_write, - }, - { - .name = "wan", - .init = wan_init, - .read = wan_read, - .write = wan_write, - .experimental = 1, - }, - { - .name = "video", - .init = video_init, - .read = video_read, - .write = video_write, - .exit = video_exit, - }, - { - .name = "light", - .init = light_init, - .read = light_read, - .write = light_write, - }, -#ifdef CONFIG_ACPI_IBM_DOCK - { - .name = "dock", - .read = dock_read, - .write = dock_write, - .notify = dock_notify, - .handle = &dock_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, - { - .name = "dock", - .hid = IBM_PCI_HID, - .notify = dock_notify, - .handle = &pci_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif -#ifdef CONFIG_ACPI_IBM_BAY - { - .name = "bay", - .init = bay_init, - .read = bay_read, - .write = bay_write, - .notify = bay_notify, - .handle = &bay_handle, - .type = ACPI_SYSTEM_NOTIFY, - }, -#endif /* CONFIG_ACPI_IBM_BAY */ - { - .name = "cmos", - .read = cmos_read, - .write = cmos_write, - }, - { - .name = "led", - .init = led_init, - .read = led_read, - .write = led_write, - }, - { - .name = "beep", - .read = beep_read, - .write = beep_write, - }, - { - .name = "thermal", - .init = thermal_init, - .read = thermal_read, - }, - { - .name = "ecdump", - .read = ecdump_read, - .write = ecdump_write, - .experimental = 1, - }, - { - .name = "brightness", - .read = brightness_read, - .write = brightness_write, - .init = brightness_init, - .exit = brightness_exit, - }, - { - .name = "volume", - .read = volume_read, - .write = volume_write, - }, - { - .name = "fan", - .read = fan_read, - .write = fan_write, - .init = fan_init, - .exit = fan_exit, - .experimental = 1, - }, -}; - -static int dispatch_read(char *page, char **start, off_t off, int count, - int *eof, void *data) -{ - struct ibm_struct *ibm = data; - int len; - - if (!ibm || !ibm->read) - return -EINVAL; - - len = ibm->read(page); - if (len < 0) - return len; - - if (len <= off + count) - *eof = 1; - *start = page + off; - len -= off; - if (len > count) - len = count; - if (len < 0) - len = 0; - - return len; -} - -static int dispatch_write(struct file *file, const char __user * userbuf, - unsigned long count, void *data) -{ - struct ibm_struct *ibm = data; - char *kernbuf; - int ret; - - if (!ibm || !ibm->write) - return -EINVAL; - - kernbuf = kmalloc(count + 2, GFP_KERNEL); - if (!kernbuf) - return -ENOMEM; - - if (copy_from_user(kernbuf, userbuf, count)) { - kfree(kernbuf); - return -EFAULT; - } - - kernbuf[count] = 0; - strcat(kernbuf, ","); - ret = ibm->write(kernbuf); - if (ret == 0) - ret = count; - - kfree(kernbuf); - - return ret; -} - -static void dispatch_notify(acpi_handle handle, u32 event, void *data) -{ - struct ibm_struct *ibm = data; - - if (!ibm || !ibm->notify) - return; - - ibm->notify(ibm, event); -} - -static int __init setup_notify(struct ibm_struct *ibm) -{ - acpi_status status; - int ret; - - if (!*ibm->handle) - return 0; - - ret = acpi_bus_get_device(*ibm->handle, &ibm->device); - if (ret < 0) { - printk(IBM_ERR "%s device not present\n", ibm->name); - return -ENODEV; - } - - acpi_driver_data(ibm->device) = ibm; - sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); - - status = acpi_install_notify_handler(*ibm->handle, ibm->type, - dispatch_notify, ibm); - if (ACPI_FAILURE(status)) { - if (status == AE_ALREADY_EXISTS) { - printk(IBM_NOTICE "another device driver is already handling %s events\n", - ibm->name); - } else { - printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n", - ibm->name, status); - } - return -ENODEV; - } - ibm->notify_installed = 1; - return 0; -} - -static int __init ibm_device_add(struct acpi_device *device) -{ - return 0; -} - -static int __init register_driver(struct ibm_struct *ibm) -{ - int ret; - - ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); - if (!ibm->driver) { - printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); - return -1; - } - - sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name); - ibm->driver->ids = ibm->hid; - ibm->driver->ops.add = &ibm_device_add; - - ret = acpi_bus_register_driver(ibm->driver); - if (ret < 0) { - printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", - ibm->hid, ret); - kfree(ibm->driver); - } - - return ret; -} - -static void ibm_exit(struct ibm_struct *ibm); - -static int __init ibm_init(struct ibm_struct *ibm) -{ - int ret; - struct proc_dir_entry *entry; - - if (ibm->experimental && !experimental) - return 0; - - if (ibm->hid) { - ret = register_driver(ibm); - if (ret < 0) - return ret; - ibm->driver_registered = 1; - } - - if (ibm->init) { - ret = ibm->init(); - if (ret != 0) - return ret; - ibm->init_called = 1; - } - - if (ibm->read) { - entry = create_proc_entry(ibm->name, - S_IFREG | S_IRUGO | S_IWUSR, - proc_dir); - if (!entry) { - printk(IBM_ERR "unable to create proc entry %s\n", - ibm->name); - return -ENODEV; - } - entry->owner = THIS_MODULE; - entry->data = ibm; - entry->read_proc = &dispatch_read; - if (ibm->write) - entry->write_proc = &dispatch_write; - ibm->proc_created = 1; - } - - if (ibm->notify) { - ret = setup_notify(ibm); - if (ret == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", - ibm->name); - ibm_exit(ibm); - return 0; - } - if (ret < 0) - return ret; - } - - return 0; -} - -static void ibm_exit(struct ibm_struct *ibm) -{ - if (ibm->notify_installed) - acpi_remove_notify_handler(*ibm->handle, ibm->type, - dispatch_notify); - - if (ibm->proc_created) - remove_proc_entry(ibm->name, proc_dir); - - if (ibm->init_called && ibm->exit) - ibm->exit(); - - if (ibm->driver_registered) { - acpi_bus_unregister_driver(ibm->driver); - kfree(ibm->driver); - } -} - -static void __init ibm_handle_init(char *name, - acpi_handle * handle, acpi_handle parent, - char **paths, int num_paths, char **path) -{ - int i; - acpi_status status; - - for (i = 0; i < num_paths; i++) { - status = acpi_get_handle(parent, paths[i], handle); - if (ACPI_SUCCESS(status)) { - *path = paths[i]; - return; - } - } - - *handle = NULL; -} - -#define IBM_HANDLE_INIT(object) \ - ibm_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) - -static int __init set_ibm_param(const char *val, struct kernel_param *kp) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(ibms); i++) - if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) { - if (strlen(val) > sizeof(ibms[i].param) - 2) - return -ENOSPC; - strcpy(ibms[i].param, val); - strcat(ibms[i].param, ","); - return 0; - } - - return -EINVAL; -} - -#define IBM_PARAM(feature) \ - module_param_call(feature, set_ibm_param, NULL, NULL, 0) - -IBM_PARAM(hotkey); -IBM_PARAM(bluetooth); -IBM_PARAM(video); -IBM_PARAM(light); -#ifdef CONFIG_ACPI_IBM_DOCK -IBM_PARAM(dock); -#endif -#ifdef CONFIG_ACPI_IBM_BAY -IBM_PARAM(bay); -#endif /* CONFIG_ACPI_IBM_BAY */ -IBM_PARAM(cmos); -IBM_PARAM(led); -IBM_PARAM(beep); -IBM_PARAM(ecdump); -IBM_PARAM(brightness); -IBM_PARAM(volume); -IBM_PARAM(fan); - -static void acpi_ibm_exit(void) -{ - int i; - - for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) - ibm_exit(&ibms[i]); - - if (proc_dir) - remove_proc_entry(IBM_DIR, acpi_root_dir); - - if (ibm_thinkpad_ec_found) - kfree(ibm_thinkpad_ec_found); -} - -static char* __init check_dmi_for_ec(void) -{ - struct dmi_device *dev = NULL; - char ec_fw_string[18]; - - /* - * ThinkPad T23 or newer, A31 or newer, R50e or newer, - * X32 or newer, all Z series; Some models must have an - * up-to-date BIOS or they will not be detected. - * - * See http://thinkwiki.org/wiki/List_of_DMI_IDs - */ - while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { - if (sscanf(dev->name, - "IBM ThinkPad Embedded Controller -[%17c", - ec_fw_string) == 1) { - ec_fw_string[sizeof(ec_fw_string) - 1] = 0; - ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; - return kstrdup(ec_fw_string, GFP_KERNEL); - } - } - return NULL; -} - -static int __init acpi_ibm_init(void) -{ - int ret, i; - - if (acpi_disabled) - return -ENODEV; - - /* ec is required because many other handles are relative to it */ - IBM_HANDLE_INIT(ec); - if (!ec_handle) { - printk(IBM_ERR "ec object not found\n"); - return -ENODEV; - } - - /* Models with newer firmware report the EC in DMI */ - ibm_thinkpad_ec_found = check_dmi_for_ec(); - - /* these handles are not required */ - IBM_HANDLE_INIT(vid); - IBM_HANDLE_INIT(vid2); - IBM_HANDLE_INIT(ledb); - IBM_HANDLE_INIT(led); - IBM_HANDLE_INIT(hkey); - IBM_HANDLE_INIT(lght); - IBM_HANDLE_INIT(cmos); -#ifdef CONFIG_ACPI_IBM_DOCK - IBM_HANDLE_INIT(dock); -#endif - IBM_HANDLE_INIT(pci); -#ifdef CONFIG_ACPI_IBM_BAY - IBM_HANDLE_INIT(bay); - if (bay_handle) - IBM_HANDLE_INIT(bay_ej); - IBM_HANDLE_INIT(bay2); - if (bay2_handle) - IBM_HANDLE_INIT(bay2_ej); -#endif /* CONFIG_ACPI_IBM_BAY */ - IBM_HANDLE_INIT(beep); - IBM_HANDLE_INIT(ecrd); - IBM_HANDLE_INIT(ecwr); - IBM_HANDLE_INIT(fans); - IBM_HANDLE_INIT(gfan); - IBM_HANDLE_INIT(sfan); - - proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); - if (!proc_dir) { - printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); - acpi_ibm_exit(); - return -ENODEV; - } - proc_dir->owner = THIS_MODULE; - - for (i = 0; i < ARRAY_SIZE(ibms); i++) { - ret = ibm_init(&ibms[i]); - if (ret >= 0 && *ibms[i].param) - ret = ibms[i].write(ibms[i].param); - if (ret < 0) { - acpi_ibm_exit(); - return ret; - } - } - - return 0; -} - -module_init(acpi_ibm_init); -module_exit(acpi_ibm_exit); diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 99d1516..ddc9e20 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -44,6 +44,7 @@ #include #include #include #include +#include #include #include @@ -1024,11 +1025,13 @@ #endif acpi_processor_ppc_init(); + cpuidle_register_driver(&acpi_idle_driver); return 0; } static void __exit acpi_processor_exit(void) { + cpuidle_unregister_driver(&acpi_idle_driver); acpi_processor_ppc_exit(); diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index cdf7894..2b17413 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -40,6 +40,7 @@ #include #include /* need_resched() */ #include #include +#include /* * Include the apic definitions for x86 to have the APIC timer related defines @@ -70,25 +71,20 @@ #define ACPI_PROCESSOR_CLASS #define _COMPONENT ACPI_PROCESSOR_COMPONENT ACPI_MODULE_NAME("processor_idle"); #define ACPI_PROCESSOR_FILE_POWER "power" -#define US_TO_PM_TIMER_TICKS(t) ((t * (PM_TIMER_FREQUENCY/1000)) / 1000) -#define C2_OVERHEAD 4 /* 1us (3.579 ticks per us) */ -#define C3_OVERHEAD 4 /* 1us (3.579 ticks per us) */ -static void (*pm_idle_save) (void) __read_mostly; +#define PM_TIMER_TICKS_TO_US(p) (((p) * 1000)/(PM_TIMER_FREQUENCY/1000)) +#define C2_OVERHEAD 1 /* 1us */ +#define C3_OVERHEAD 1 /* 1us */ + module_param(max_cstate, uint, 0644); static unsigned int nocst __read_mostly; module_param(nocst, uint, 0000); -/* - * bm_history -- bit-mask with a bit per jiffy of bus-master activity - * 1000 HZ: 0xFFFFFFFF: 32 jiffies = 32ms - * 800 HZ: 0xFFFFFFFF: 32 jiffies = 40ms - * 100 HZ: 0x0000000F: 4 jiffies = 40ms - * reduce history for more aggressive entry into C3 - */ -static unsigned int bm_history __read_mostly = - (HZ >= 800 ? 0xFFFFFFFF : ((1U << (HZ / 25)) - 1)); -module_param(bm_history, uint, 0644); +static unsigned use_ipi = 2; +module_param(use_ipi, uint, 0644); +MODULE_PARM_DESC(use_ipi, "IPI (vs. LAPIC) irqs for not waking up from C2/C3" + " machines. 0=apic, 1=ipi, 2=auto\n"); + /* -------------------------------------------------------------------------- Power Management -------------------------------------------------------------------------- */ @@ -174,88 +170,6 @@ static struct dmi_system_id __cpuinitdat {}, }; -static inline u32 ticks_elapsed(u32 t1, u32 t2) -{ - if (t2 >= t1) - return (t2 - t1); - else if (!(acpi_gbl_FADT.flags & ACPI_FADT_32BIT_TIMER)) - return (((0x00FFFFFF - t1) + t2) & 0x00FFFFFF); - else - return ((0xFFFFFFFF - t1) + t2); -} - -static void -acpi_processor_power_activate(struct acpi_processor *pr, - struct acpi_processor_cx *new) -{ - struct acpi_processor_cx *old; - - if (!pr || !new) - return; - - old = pr->power.state; - - if (old) - old->promotion.count = 0; - new->demotion.count = 0; - - /* Cleanup from old state. */ - if (old) { - switch (old->type) { - case ACPI_STATE_C3: - /* Disable bus master reload */ - if (new->type != ACPI_STATE_C3 && pr->flags.bm_check) - acpi_set_register(ACPI_BITREG_BUS_MASTER_RLD, 0); - break; - } - } - - /* Prepare to use new state. */ - switch (new->type) { - case ACPI_STATE_C3: - /* Enable bus master reload */ - if (old->type != ACPI_STATE_C3 && pr->flags.bm_check) - acpi_set_register(ACPI_BITREG_BUS_MASTER_RLD, 1); - break; - } - - pr->power.state = new; - - return; -} - -static void acpi_safe_halt(void) -{ - current_thread_info()->status &= ~TS_POLLING; - /* - * TS_POLLING-cleared state must be visible before we - * test NEED_RESCHED: - */ - smp_mb(); - if (!need_resched()) - safe_halt(); - current_thread_info()->status |= TS_POLLING; -} - -static atomic_t c3_cpu_count; - -/* Common C-state entry for C2, C3, .. */ -static void acpi_cstate_enter(struct acpi_processor_cx *cstate) -{ - if (cstate->space_id == ACPI_CSTATE_FFH) { - /* Call into architectural FFH based C-state */ - acpi_processor_ffh_cstate_enter(cstate); - } else { - int unused; - /* IO port based C-state */ - inb(cstate->address); - /* Dummy wait op - must do something useless after P_LVL2 read - because chipsets cannot guarantee that STPCLK# signal - gets asserted in time to freeze execution properly. */ - unused = inl(acpi_gbl_FADT.xpm_timer_block.address); - } -} - #ifdef ARCH_APICTIMER_STOPS_ON_C3 /* @@ -331,376 +245,6 @@ static void acpi_state_timer_broadcast(s } #endif - -static void acpi_processor_idle(void) -{ - struct acpi_processor *pr = NULL; - struct acpi_processor_cx *cx = NULL; - struct acpi_processor_cx *next_state = NULL; - int sleep_ticks = 0; - u32 t1, t2 = 0; - - pr = processors[smp_processor_id()]; - if (!pr) - return; - - /* - * Interrupts must be disabled during bus mastering calculations and - * for C2/C3 transitions. - */ - local_irq_disable(); - - /* - * Check whether we truly need to go idle, or should - * reschedule: - */ - if (unlikely(need_resched())) { - local_irq_enable(); - return; - } - - cx = pr->power.state; - if (!cx) { - if (pm_idle_save) - pm_idle_save(); - else - acpi_safe_halt(); - return; - } - - /* - * Check BM Activity - * ----------------- - * Check for bus mastering activity (if required), record, and check - * for demotion. - */ - if (pr->flags.bm_check) { - u32 bm_status = 0; - unsigned long diff = jiffies - pr->power.bm_check_timestamp; - - if (diff > 31) - diff = 31; - - pr->power.bm_activity <<= diff; - - acpi_get_register(ACPI_BITREG_BUS_MASTER_STATUS, &bm_status); - if (bm_status) { - pr->power.bm_activity |= 0x1; - acpi_set_register(ACPI_BITREG_BUS_MASTER_STATUS, 1); - } - /* - * PIIX4 Erratum #18: Note that BM_STS doesn't always reflect - * the true state of bus mastering activity; forcing us to - * manually check the BMIDEA bit of each IDE channel. - */ - else if (errata.piix4.bmisx) { - if ((inb_p(errata.piix4.bmisx + 0x02) & 0x01) - || (inb_p(errata.piix4.bmisx + 0x0A) & 0x01)) - pr->power.bm_activity |= 0x1; - } - - pr->power.bm_check_timestamp = jiffies; - - /* - * If bus mastering is or was active this jiffy, demote - * to avoid a faulty transition. Note that the processor - * won't enter a low-power state during this call (to this - * function) but should upon the next. - * - * TBD: A better policy might be to fallback to the demotion - * state (use it for this quantum only) istead of - * demoting -- and rely on duration as our sole demotion - * qualification. This may, however, introduce DMA - * issues (e.g. floppy DMA transfer overrun/underrun). - */ - if ((pr->power.bm_activity & 0x1) && - cx->demotion.threshold.bm) { - local_irq_enable(); - next_state = cx->demotion.state; - goto end; - } - } - -#ifdef CONFIG_HOTPLUG_CPU - /* - * Check for P_LVL2_UP flag before entering C2 and above on - * an SMP system. We do it here instead of doing it at _CST/P_LVL - * detection phase, to work cleanly with logical CPU hotplug. - */ - if ((cx->type != ACPI_STATE_C1) && (num_online_cpus() > 1) && - !pr->flags.has_cst && !(acpi_gbl_FADT.flags & ACPI_FADT_C2_MP_SUPPORTED)) - cx = &pr->power.states[ACPI_STATE_C1]; -#endif - - /* - * Sleep: - * ------ - * Invoke the current Cx state to put the processor to sleep. - */ - if (cx->type == ACPI_STATE_C2 || cx->type == ACPI_STATE_C3) { - current_thread_info()->status &= ~TS_POLLING; - /* - * TS_POLLING-cleared state must be visible before we - * test NEED_RESCHED: - */ - smp_mb(); - if (need_resched()) { - current_thread_info()->status |= TS_POLLING; - local_irq_enable(); - return; - } - } - - switch (cx->type) { - - case ACPI_STATE_C1: - /* - * Invoke C1. - * Use the appropriate idle routine, the one that would - * be used without acpi C-states. - */ - if (pm_idle_save) - pm_idle_save(); - else - acpi_safe_halt(); - - /* - * TBD: Can't get time duration while in C1, as resumes - * go to an ISR rather than here. Need to instrument - * base interrupt handler. - */ - sleep_ticks = 0xFFFFFFFF; - break; - - case ACPI_STATE_C2: - /* Get start time (ticks) */ - t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); - /* Invoke C2 */ - acpi_state_timer_broadcast(pr, cx, 1); - acpi_cstate_enter(cx); - /* Get end time (ticks) */ - t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); - -#ifdef CONFIG_GENERIC_TIME - /* TSC halts in C2, so notify users */ - mark_tsc_unstable(); -#endif - /* Re-enable interrupts */ - local_irq_enable(); - current_thread_info()->status |= TS_POLLING; - /* Compute time (ticks) that we were actually asleep */ - sleep_ticks = - ticks_elapsed(t1, t2) - cx->latency_ticks - C2_OVERHEAD; - acpi_state_timer_broadcast(pr, cx, 0); - break; - - case ACPI_STATE_C3: - - if (pr->flags.bm_check) { - if (atomic_inc_return(&c3_cpu_count) == - num_online_cpus()) { - /* - * All CPUs are trying to go to C3 - * Disable bus master arbitration - */ - acpi_set_register(ACPI_BITREG_ARB_DISABLE, 1); - } - } else { - /* SMP with no shared cache... Invalidate cache */ - ACPI_FLUSH_CPU_CACHE(); - } - - /* Get start time (ticks) */ - t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); - /* Invoke C3 */ - acpi_state_timer_broadcast(pr, cx, 1); - acpi_cstate_enter(cx); - /* Get end time (ticks) */ - t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); - if (pr->flags.bm_check) { - /* Enable bus master arbitration */ - atomic_dec(&c3_cpu_count); - acpi_set_register(ACPI_BITREG_ARB_DISABLE, 0); - } - -#ifdef CONFIG_GENERIC_TIME - /* TSC halts in C3, so notify users */ - mark_tsc_unstable(); -#endif - /* Re-enable interrupts */ - local_irq_enable(); - current_thread_info()->status |= TS_POLLING; - /* Compute time (ticks) that we were actually asleep */ - sleep_ticks = - ticks_elapsed(t1, t2) - cx->latency_ticks - C3_OVERHEAD; - acpi_state_timer_broadcast(pr, cx, 0); - break; - - default: - local_irq_enable(); - return; - } - cx->usage++; - if ((cx->type != ACPI_STATE_C1) && (sleep_ticks > 0)) - cx->time += sleep_ticks; - - next_state = pr->power.state; - -#ifdef CONFIG_HOTPLUG_CPU - /* Don't do promotion/demotion */ - if ((cx->type == ACPI_STATE_C1) && (num_online_cpus() > 1) && - !pr->flags.has_cst && !(acpi_gbl_FADT.flags & ACPI_FADT_C2_MP_SUPPORTED)) { - next_state = cx; - goto end; - } -#endif - - /* - * Promotion? - * ---------- - * Track the number of longs (time asleep is greater than threshold) - * and promote when the count threshold is reached. Note that bus - * mastering activity may prevent promotions. - * Do not promote above max_cstate. - */ - if (cx->promotion.state && - ((cx->promotion.state - pr->power.states) <= max_cstate)) { - if (sleep_ticks > cx->promotion.threshold.ticks && - cx->promotion.state->latency <= system_latency_constraint()) { - cx->promotion.count++; - cx->demotion.count = 0; - if (cx->promotion.count >= - cx->promotion.threshold.count) { - if (pr->flags.bm_check) { - if (! - (pr->power.bm_activity & cx-> - promotion.threshold.bm)) { - next_state = - cx->promotion.state; - goto end; - } - } else { - next_state = cx->promotion.state; - goto end; - } - } - } - } - - /* - * Demotion? - * --------- - * Track the number of shorts (time asleep is less than time threshold) - * and demote when the usage threshold is reached. - */ - if (cx->demotion.state) { - if (sleep_ticks < cx->demotion.threshold.ticks) { - cx->demotion.count++; - cx->promotion.count = 0; - if (cx->demotion.count >= cx->demotion.threshold.count) { - next_state = cx->demotion.state; - goto end; - } - } - } - - end: - /* - * Demote if current state exceeds max_cstate - * or if the latency of the current state is unacceptable - */ - if ((pr->power.state - pr->power.states) > max_cstate || - pr->power.state->latency > system_latency_constraint()) { - if (cx->demotion.state) - next_state = cx->demotion.state; - } - - /* - * New Cx State? - * ------------- - * If we're going to start using a new Cx state we must clean up - * from the previous and prepare to use the new. - */ - if (next_state != pr->power.state) - acpi_processor_power_activate(pr, next_state); -} - -static int acpi_processor_set_power_policy(struct acpi_processor *pr) -{ - unsigned int i; - unsigned int state_is_set = 0; - struct acpi_processor_cx *lower = NULL; - struct acpi_processor_cx *higher = NULL; - struct acpi_processor_cx *cx; - - - if (!pr) - return -EINVAL; - - /* - * This function sets the default Cx state policy (OS idle handler). - * Our scheme is to promote quickly to C2 but more conservatively - * to C3. We're favoring C2 for its characteristics of low latency - * (quick response), good power savings, and ability to allow bus - * mastering activity. Note that the Cx state policy is completely - * customizable and can be altered dynamically. - */ - - /* startup state */ - for (i = 1; i < ACPI_PROCESSOR_MAX_POWER; i++) { - cx = &pr->power.states[i]; - if (!cx->valid) - continue; - - if (!state_is_set) - pr->power.state = cx; - state_is_set++; - break; - } - - if (!state_is_set) - return -ENODEV; - - /* demotion */ - for (i = 1; i < ACPI_PROCESSOR_MAX_POWER; i++) { - cx = &pr->power.states[i]; - if (!cx->valid) - continue; - - if (lower) { - cx->demotion.state = lower; - cx->demotion.threshold.ticks = cx->latency_ticks; - cx->demotion.threshold.count = 1; - if (cx->type == ACPI_STATE_C3) - cx->demotion.threshold.bm = bm_history; - } - - lower = cx; - } - - /* promotion */ - for (i = (ACPI_PROCESSOR_MAX_POWER - 1); i > 0; i--) { - cx = &pr->power.states[i]; - if (!cx->valid) - continue; - - if (higher) { - cx->promotion.state = higher; - cx->promotion.threshold.ticks = cx->latency_ticks; - if (cx->type >= ACPI_STATE_C2) - cx->promotion.threshold.count = 4; - else - cx->promotion.threshold.count = 10; - if (higher->type == ACPI_STATE_C3) - cx->promotion.threshold.bm = bm_history; - } - - higher = cx; - } - - return 0; -} - static int acpi_processor_get_power_info_fadt(struct acpi_processor *pr) { @@ -918,7 +462,7 @@ static void acpi_processor_power_verify_ * Normalize the C2 latency to expidite policy */ cx->valid = 1; - cx->latency_ticks = US_TO_PM_TIMER_TICKS(cx->latency); + cx->latency_ticks = cx->latency; return; } @@ -992,7 +536,7 @@ static void acpi_processor_power_verify_ * use this in our C3 policy */ cx->valid = 1; - cx->latency_ticks = US_TO_PM_TIMER_TICKS(cx->latency); + cx->latency_ticks = cx->latency; return; } @@ -1058,18 +602,6 @@ static int acpi_processor_get_power_info pr->power.count = acpi_processor_power_verify(pr); /* - * Set Default Policy - * ------------------ - * Now that we know which states are supported, set the default - * policy. Note that this policy can be changed dynamically - * (e.g. encourage deeper sleeps to conserve battery life when - * not on AC). - */ - result = acpi_processor_set_power_policy(pr); - if (result) - return result; - - /* * if one state of type C2 or C3 is available, mark this * CPU as being "idle manageable" */ @@ -1086,9 +618,6 @@ static int acpi_processor_get_power_info int acpi_processor_cst_has_changed(struct acpi_processor *pr) { - int result = 0; - - if (!pr) return -EINVAL; @@ -1099,16 +628,8 @@ int acpi_processor_cst_has_changed(struc if (!pr->flags.power_setup_done) return -ENODEV; - /* Fall back to the default idle loop */ - pm_idle = pm_idle_save; - synchronize_sched(); /* Relies on interrupts forcing exit from idle. */ - - pr->flags.power = 0; - result = acpi_processor_get_power_info(pr); - if ((pr->flags.power == 1) && (pr->flags.power_setup_done)) - pm_idle = acpi_processor_idle; - - return result; + acpi_processor_get_power_info(pr); + return cpuidle_force_redetect(&per_cpu(cpuidle_devices, pr->id)); } /* proc interface */ @@ -1194,30 +715,6 @@ static const struct file_operations acpi .release = single_release, }; -#ifdef CONFIG_SMP -static void smp_callback(void *v) -{ - /* we already woke the CPU up, nothing more to do */ -} - -/* - * This function gets called when a part of the kernel has a new latency - * requirement. This means we need to get all processors out of their C-state, - * and then recalculate a new suitable C-state. Just do a cross-cpu IPI; that - * wakes them all right up. - */ -static int acpi_processor_latency_notify(struct notifier_block *b, - unsigned long l, void *v) -{ - smp_call_function(smp_callback, NULL, 0, 1); - return NOTIFY_OK; -} - -static struct notifier_block acpi_processor_latency_notifier = { - .notifier_call = acpi_processor_latency_notify, -}; -#endif - int __cpuinit acpi_processor_power_init(struct acpi_processor *pr, struct acpi_device *device) { @@ -1234,9 +731,6 @@ int __cpuinit acpi_processor_power_init( "ACPI: processor limited to max C-state %d\n", max_cstate); first_run++; -#ifdef CONFIG_SMP - register_latency_notifier(&acpi_processor_latency_notifier); -#endif } if (!pr) @@ -1253,6 +747,7 @@ #endif acpi_processor_get_power_info(pr); + /* * Install the idle handler if processor power management is supported. * Note that we use previously set idle handler will be used on @@ -1265,11 +760,6 @@ #endif printk(" C%d[C%d]", i, pr->power.states[i].type); printk(")\n"); - - if (pr->id == 0) { - pm_idle_save = pm_idle; - pm_idle = acpi_processor_idle; - } } /* 'power' [R] */ @@ -1297,21 +787,332 @@ int acpi_processor_power_exit(struct acp if (acpi_device_dir(device)) remove_proc_entry(ACPI_PROCESSOR_FILE_POWER, acpi_device_dir(device)); + return 0; +} + +/** + * ticks_elapsed - a helper function that determines how many ticks (in US) + * have elapsed between two PM Timer timestamps + * @t1: the start time + * @t2: the end time + */ +static inline u32 ticks_elapsed(u32 t1, u32 t2) +{ + if (t2 >= t1) + return PM_TIMER_TICKS_TO_US(t2 - t1); + else if (!(acpi_gbl_FADT.flags & ACPI_FADT_32BIT_TIMER)) + return PM_TIMER_TICKS_TO_US(((0x00FFFFFF - t1) + t2) & 0x00FFFFFF); + else + return PM_TIMER_TICKS_TO_US((0xFFFFFFFF - t1) + t2); +} - /* Unregister the idle handler when processor #0 is removed. */ - if (pr->id == 0) { - pm_idle = pm_idle_save; +/** + * acpi_idle_update_bm_rld - updates the BM_RLD bit depending on target state + * @pr: the processor + * @target: the new target state + */ +static inline void acpi_idle_update_bm_rld(struct acpi_processor *pr, + struct acpi_processor_cx *target) +{ + if (pr->flags.bm_rld_set && target->type != ACPI_STATE_C3) { + acpi_set_register(ACPI_BITREG_BUS_MASTER_RLD, 0); + pr->flags.bm_rld_set = 0; + } - /* - * We are about to unload the current idle thread pm callback - * (pm_idle), Wait for all processors to update cached/local - * copies of pm_idle before proceeding. - */ - cpu_idle_wait(); -#ifdef CONFIG_SMP - unregister_latency_notifier(&acpi_processor_latency_notifier); + if (!pr->flags.bm_rld_set && target->type == ACPI_STATE_C3) { + acpi_set_register(ACPI_BITREG_BUS_MASTER_RLD, 1); + pr->flags.bm_rld_set = 1; + } +} + +/** + * acpi_idle_do_entry - a helper function that does C2 and C3 type entry + * @cx: cstate data + */ +static inline void acpi_idle_do_entry(struct acpi_processor_cx *cx) +{ + if (cx->space_id == ACPI_CSTATE_FFH) { + /* Call into architectural FFH based C-state */ + acpi_processor_ffh_cstate_enter(cx); + } else { + int unused; + /* IO port based C-state */ + inb(cx->address); + /* Dummy wait op - must do something useless after P_LVL2 read + because chipsets cannot guarantee that STPCLK# signal + gets asserted in time to freeze execution properly. */ + unused = inl(acpi_gbl_FADT.xpm_timer_block.address); + } +} + +/** + * acpi_idle_enter_c1 - enters an ACPI C1 state-type + * @dev: the target CPU + * @state: the state data + * + * This is equivalent to the HALT instruction. + */ +static int acpi_idle_enter_c1(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct acpi_processor *pr; + struct acpi_processor_cx *cx = cpuidle_get_statedata(state); + pr = processors[smp_processor_id()]; + + if (unlikely(!pr)) + return 0; + + if (pr->flags.bm_check) + acpi_idle_update_bm_rld(pr, cx); + + current_thread_info()->status &= ~TS_POLLING; + /* + * TS_POLLING-cleared state must be visible before we test + * NEED_RESCHED: + */ + smp_mb(); + if (!need_resched()) + safe_halt(); + current_thread_info()->status |= TS_POLLING; + + cx->usage++; + + return 0; +} + +/** + * acpi_idle_enter_c2 - enters an ACPI C2 state-type + * @dev: the target CPU + * @state: the state data + */ +static int acpi_idle_enter_c2(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct acpi_processor *pr; + struct acpi_processor_cx *cx = cpuidle_get_statedata(state); + u32 t1, t2; + pr = processors[smp_processor_id()]; + + if (unlikely(!pr)) + return 0; + + if (pr->flags.bm_check) + acpi_idle_update_bm_rld(pr, cx); + + local_irq_disable(); + current_thread_info()->status &= ~TS_POLLING; + /* + * TS_POLLING-cleared state must be visible before we test + * NEED_RESCHED: + */ + smp_mb(); + + if (unlikely(need_resched())) { + current_thread_info()->status |= TS_POLLING; + local_irq_enable(); + return 0; + } + + t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); + acpi_state_timer_broadcast(pr, cx, 1); + acpi_idle_do_entry(cx); + t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); + +#ifdef CONFIG_GENERIC_TIME + /* TSC halts in C2, so notify users */ + mark_tsc_unstable(); #endif + + local_irq_enable(); + current_thread_info()->status |= TS_POLLING; + + cx->usage++; + + acpi_state_timer_broadcast(pr, cx, 0); + return ticks_elapsed(t1, t2); +} + +static int c3_cpu_count; +static DEFINE_SPINLOCK(c3_lock); + +/** + * acpi_idle_enter_c3 - enters an ACPI C3 state-type + * @dev: the target CPU + * @state: the state data + * + * Similar to C2 entry, except special bus master handling is needed. + */ +static int acpi_idle_enter_c3(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct acpi_processor *pr; + struct acpi_processor_cx *cx = cpuidle_get_statedata(state); + u32 t1, t2; + pr = processors[smp_processor_id()]; + + if (unlikely(!pr)) + return 0; + + if (pr->flags.bm_check) + acpi_idle_update_bm_rld(pr, cx); + + local_irq_disable(); + current_thread_info()->status &= ~TS_POLLING; + /* + * TS_POLLING-cleared state must be visible before we test + * NEED_RESCHED: + */ + smp_mb(); + + if (unlikely(need_resched())) { + current_thread_info()->status |= TS_POLLING; + local_irq_enable(); + return 0; + } + + /* disable bus master */ + if (pr->flags.bm_check) { + spin_lock(&c3_lock); + c3_cpu_count++; + if (c3_cpu_count == num_online_cpus()) { + /* + * All CPUs are trying to go to C3 + * Disable bus master arbitration + */ + acpi_set_register(ACPI_BITREG_ARB_DISABLE, 1); + } + spin_unlock(&c3_lock); + } else { + /* SMP with no shared cache... Invalidate cache */ + ACPI_FLUSH_CPU_CACHE(); + } + + /* Get start time (ticks) */ + t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); + acpi_state_timer_broadcast(pr, cx, 1); + acpi_idle_do_entry(cx); + t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); + + if (pr->flags.bm_check) { + spin_lock(&c3_lock); + /* Enable bus master arbitration */ + if (c3_cpu_count == num_online_cpus()) + acpi_set_register(ACPI_BITREG_ARB_DISABLE, 0); + c3_cpu_count--; + spin_unlock(&c3_lock); } +#ifdef CONFIG_GENERIC_TIME + /* TSC halts in C3, so notify users */ + mark_tsc_unstable(); +#endif + + local_irq_enable(); + current_thread_info()->status |= TS_POLLING; + + cx->usage++; + + acpi_state_timer_broadcast(pr, cx, 0); + return ticks_elapsed(t1, t2); +} + +/** + * acpi_idle_bm_check - checks if bus master activity was detected + */ +static int acpi_idle_bm_check(void) +{ + u32 bm_status = 0; + + acpi_get_register(ACPI_BITREG_BUS_MASTER_STATUS, &bm_status); + if (bm_status) + acpi_set_register(ACPI_BITREG_BUS_MASTER_STATUS, 1); + /* + * PIIX4 Erratum #18: Note that BM_STS doesn't always reflect + * the true state of bus mastering activity; forcing us to + * manually check the BMIDEA bit of each IDE channel. + */ + else if (errata.piix4.bmisx) { + if ((inb_p(errata.piix4.bmisx + 0x02) & 0x01) + || (inb_p(errata.piix4.bmisx + 0x0A) & 0x01)) + bm_status = 1; + } + return bm_status; +} + +/** + * acpi_idle_init - attaches the driver to a CPU + * @dev: the CPU + */ +static int acpi_idle_init(struct cpuidle_device *dev) +{ + int cpu = dev->cpu; + int i, count = 0; + struct acpi_processor_cx *cx; + struct cpuidle_state *state; + + struct acpi_processor *pr = processors[cpu]; + + if (!pr->flags.power_setup_done) + return -EINVAL; + + if (pr->flags.power == 0) { + return -EINVAL; + } + + for (i = 1; i < ACPI_PROCESSOR_MAX_POWER; i++) { + cx = &pr->power.states[i]; + state = &dev->states[count]; + + if (!cx->valid) + continue; + +#ifdef CONFIG_HOTPLUG_CPU + if ((cx->type != ACPI_STATE_C1) && (num_online_cpus() > 1) && + !pr->flags.has_cst && + !(acpi_gbl_FADT.flags & ACPI_FADT_C2_MP_SUPPORTED)) + continue; +#endif + cpuidle_set_statedata(state, cx); + + state->exit_latency = cx->latency; + state->target_residency = cx->latency * 6; + state->power_usage = cx->power; + + state->flags = 0; + switch (cx->type) { + case ACPI_STATE_C1: + state->flags |= CPUIDLE_FLAG_SHALLOW; + state->enter = acpi_idle_enter_c1; + break; + + case ACPI_STATE_C2: + state->flags |= CPUIDLE_FLAG_BALANCED; + state->flags |= CPUIDLE_FLAG_TIME_VALID; + state->enter = acpi_idle_enter_c2; + break; + + case ACPI_STATE_C3: + state->flags |= CPUIDLE_FLAG_DEEP; + state->flags |= CPUIDLE_FLAG_TIME_VALID; + state->flags |= CPUIDLE_FLAG_CHECK_BM; + state->enter = acpi_idle_enter_c3; + break; + } + + count++; + } + + if (!count) + return -EINVAL; + + dev->state_count = count; return 0; } + +struct cpuidle_driver acpi_idle_driver = { + .name = "acpi_idle", + .init = acpi_idle_init, + .redetect = acpi_idle_init, + .bm_check = acpi_idle_bm_check, + .owner = THIS_MODULE, +}; diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 59640d9..c1bae10 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -30,30 +30,10 @@ #include #include #include #include -#include +#include +#include #include -#include "i2c_ec.h" - -#define DEF_CAPACITY_UNIT 3 -#define MAH_CAPACITY_UNIT 1 -#define MWH_CAPACITY_UNIT 2 -#define CAPACITY_UNIT DEF_CAPACITY_UNIT - -#define REQUEST_UPDATE_MODE 1 -#define QUEUE_UPDATE_MODE 2 - -#define DATA_TYPE_COMMON 0 -#define DATA_TYPE_INFO 1 -#define DATA_TYPE_STATE 2 -#define DATA_TYPE_ALARM 3 -#define DATA_TYPE_AC_STATE 4 - -extern struct proc_dir_entry *acpi_lock_ac_dir(void); -extern struct proc_dir_entry *acpi_lock_battery_dir(void); -extern void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); -extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); - #define ACPI_SBS_COMPONENT 0x00080000 #define ACPI_SBS_CLASS "sbs" #define ACPI_AC_CLASS "ac_adapter" @@ -74,39 +54,75 @@ #define ACPI_SBS_BATTERY_NOTIFY_INFO 0x8 #define _COMPONENT ACPI_SBS_COMPONENT -#define MAX_SBS_BAT 4 -#define MAX_SMBUS_ERR 1 - ACPI_MODULE_NAME("sbs"); MODULE_AUTHOR("Rich Townsend"); MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); MODULE_LICENSE("GPL"); -static struct semaphore sbs_sem; +#define xmsleep(t) msleep(t) + +#define ACPI_EC_SMB_PRTCL 0x00 /* protocol, PEC */ + +#define ACPI_EC_SMB_STS 0x01 /* status */ +#define ACPI_EC_SMB_ADDR 0x02 /* address */ +#define ACPI_EC_SMB_CMD 0x03 /* command */ +#define ACPI_EC_SMB_DATA 0x04 /* 32 data registers */ +#define ACPI_EC_SMB_BCNT 0x24 /* number of data bytes */ -#define UPDATE_MODE QUEUE_UPDATE_MODE -/* REQUEST_UPDATE_MODE QUEUE_UPDATE_MODE */ -#define UPDATE_INFO_MODE 0 -#define UPDATE_TIME 60 -#define UPDATE_TIME2 0 +#define ACPI_EC_SMB_STS_DONE 0x80 +#define ACPI_EC_SMB_STS_STATUS 0x1f -static int capacity_mode = CAPACITY_UNIT; -static int update_mode = UPDATE_MODE; -static int update_info_mode = UPDATE_INFO_MODE; -static int update_time = UPDATE_TIME; -static int update_time2 = UPDATE_TIME2; +#define ACPI_EC_SMB_PRTCL_WRITE 0x00 +#define ACPI_EC_SMB_PRTCL_READ 0x01 +#define ACPI_EC_SMB_PRTCL_WORD_DATA 0x08 +#define ACPI_EC_SMB_PRTCL_BLOCK_DATA 0x0a -module_param(capacity_mode, int, 0); -module_param(update_mode, int, 0); -module_param(update_info_mode, int, 0); -module_param(update_time, int, 0); -module_param(update_time2, int, 0); +#define ACPI_EC_SMB_TRANSACTION_SLEEP 1 +#define ACPI_EC_SMB_ACCESS_SLEEP1 1 +#define ACPI_EC_SMB_ACCESS_SLEEP2 10 + +#define DEF_CAPACITY_UNIT 3 +#define MAH_CAPACITY_UNIT 1 +#define MWH_CAPACITY_UNIT 2 +#define CAPACITY_UNIT DEF_CAPACITY_UNIT + +#define REQUEST_UPDATE_MODE 1 +#define QUEUE_UPDATE_MODE 2 + +#define DATA_TYPE_COMMON 0 +#define DATA_TYPE_INFO 1 +#define DATA_TYPE_STATE 2 +#define DATA_TYPE_ALARM 3 +#define DATA_TYPE_AC_STATE 4 + +extern struct proc_dir_entry *acpi_lock_ac_dir(void); +extern struct proc_dir_entry *acpi_lock_battery_dir(void); +extern void acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); +extern void acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); + +#define MAX_SBS_BAT 4 +#define ACPI_SBS_BLOCK_MAX 32 + +#define ACPI_SBS_SMBUS_READ 1 +#define ACPI_SBS_SMBUS_WRITE 2 + +#define ACPI_SBS_WORD_DATA 1 +#define ACPI_SBS_BLOCK_DATA 2 + +#define UPDATE_DELAY 10 + +/* 0 - every time, > 0 - by update_time */ +static unsigned int update_time = 120; + +static unsigned int capacity_mode = CAPACITY_UNIT; + +module_param(update_time, uint, 0644); +module_param(capacity_mode, uint, 0444); static int acpi_sbs_add(struct acpi_device *device); static int acpi_sbs_remove(struct acpi_device *device, int type); -static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus); -static void acpi_sbs_update_queue(void *data); +static int acpi_sbs_resume(struct acpi_device *device); static struct acpi_driver acpi_sbs_driver = { .name = "sbs", @@ -115,9 +131,14 @@ static struct acpi_driver acpi_sbs_drive .ops = { .add = acpi_sbs_add, .remove = acpi_sbs_remove, + .resume = acpi_sbs_resume, }, }; +struct acpi_ac { + int ac_present; +}; + struct acpi_battery_info { int capacity_mode; s16 full_charge_capacity; @@ -126,18 +147,16 @@ struct acpi_battery_info { int vscale; int ipscale; s16 serial_number; - char manufacturer_name[I2C_SMBUS_BLOCK_MAX + 3]; - char device_name[I2C_SMBUS_BLOCK_MAX + 3]; - char device_chemistry[I2C_SMBUS_BLOCK_MAX + 3]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX + 3]; + char device_name[ACPI_SBS_BLOCK_MAX + 3]; + char device_chemistry[ACPI_SBS_BLOCK_MAX + 3]; }; struct acpi_battery_state { s16 voltage; s16 amperage; s16 remaining_capacity; - s16 average_time_to_empty; - s16 average_time_to_full; - s16 battery_status; + s16 battery_state; }; struct acpi_battery_alarm { @@ -146,9 +165,9 @@ struct acpi_battery_alarm { struct acpi_battery { int alive; - int battery_present; int id; int init_state; + int battery_present; struct acpi_sbs *sbs; struct acpi_battery_info info; struct acpi_battery_state state; @@ -158,186 +177,251 @@ struct acpi_battery { struct acpi_sbs { acpi_handle handle; + int base; struct acpi_device *device; struct acpi_ec_smbus *smbus; + struct mutex mutex; int sbsm_present; int sbsm_batteries_supported; - int ac_present; struct proc_dir_entry *ac_entry; + struct acpi_ac ac; struct acpi_battery battery[MAX_SBS_BAT]; - int update_info_mode; int zombie; - int update_time; - int update_time2; struct timer_list update_timer; + int run_cnt; + int update_proc_flg; }; -static void acpi_update_delay(struct acpi_sbs *sbs); -static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type); +static int acpi_sbs_update_run(struct acpi_sbs *sbs, int id, int data_type); +static void acpi_sbs_update_time(void *data); + +union sbs_rw_data { + u16 word; + u8 block[ACPI_SBS_BLOCK_MAX + 2]; +}; + +static int acpi_ec_sbs_access(struct acpi_sbs *sbs, u16 addr, + char read_write, u8 command, int size, + union sbs_rw_data *data); /* -------------------------------------------------------------------------- SMBus Communication -------------------------------------------------------------------------- */ -static void acpi_battery_smbus_err_handler(struct acpi_ec_smbus *smbus) +static int acpi_ec_sbs_read(struct acpi_sbs *sbs, u8 address, u8 * data) { - union i2c_smbus_data data; - int result = 0; - char *err_str; - int err_number; + u8 val; + int err; - data.word = 0; + err = ec_read(sbs->base + address, &val); + if (!err) { + *data = val; + } + xmsleep(ACPI_EC_SMB_TRANSACTION_SLEEP); + return (err); +} - result = smbus->adapter.algo-> - smbus_xfer(&smbus->adapter, - ACPI_SB_SMBUS_ADDR, - 0, I2C_SMBUS_READ, 0x16, I2C_SMBUS_BLOCK_DATA, &data); +static int acpi_ec_sbs_write(struct acpi_sbs *sbs, u8 address, u8 data) +{ + int err; - err_number = (data.word & 0x000f); + err = ec_write(sbs->base + address, data); + return (err); +} - switch (data.word & 0x000f) { - case 0x0000: - err_str = "unexpected bus error"; - break; - case 0x0001: - err_str = "busy"; - break; - case 0x0002: - err_str = "reserved command"; - break; - case 0x0003: - err_str = "unsupported command"; - break; - case 0x0004: - err_str = "access denied"; +static int +acpi_ec_sbs_access(struct acpi_sbs *sbs, u16 addr, + char read_write, u8 command, int size, + union sbs_rw_data *data) +{ + unsigned char protocol, len = 0, temp[2] = { 0, 0 }; + int i; + + if (read_write == ACPI_SBS_SMBUS_READ) { + protocol = ACPI_EC_SMB_PRTCL_READ; + } else { + protocol = ACPI_EC_SMB_PRTCL_WRITE; + } + + switch (size) { + + case ACPI_SBS_WORD_DATA: + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_CMD, command); + if (read_write == ACPI_SBS_SMBUS_WRITE) { + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA, data->word); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA + 1, + data->word >> 8); + } + protocol |= ACPI_EC_SMB_PRTCL_WORD_DATA; break; - case 0x0005: - err_str = "overflow/underflow"; + case ACPI_SBS_BLOCK_DATA: + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_CMD, command); + if (read_write == ACPI_SBS_SMBUS_WRITE) { + len = min_t(u8, data->block[0], 32); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_BCNT, len); + for (i = 0; i < len; i++) + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_DATA + i, + data->block[i + 1]); + } + protocol |= ACPI_EC_SMB_PRTCL_BLOCK_DATA; break; - case 0x0006: - err_str = "bad size"; + default: + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "unsupported transaction %d", size)); + return (-1); + } + + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_ADDR, addr << 1); + acpi_ec_sbs_write(sbs, ACPI_EC_SMB_PRTCL, protocol); + + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + + if (~temp[0] & ACPI_EC_SMB_STS_DONE) { + xmsleep(ACPI_EC_SMB_ACCESS_SLEEP1); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + } + if (~temp[0] & ACPI_EC_SMB_STS_DONE) { + xmsleep(ACPI_EC_SMB_ACCESS_SLEEP2); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_STS, temp); + } + if ((~temp[0] & ACPI_EC_SMB_STS_DONE) + || (temp[0] & ACPI_EC_SMB_STS_STATUS)) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "transaction %d error", size)); + return (-1); + } + + if (read_write == ACPI_SBS_SMBUS_WRITE) { + return (0); + } + + switch (size) { + + case ACPI_SBS_WORD_DATA: + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA, temp); + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA + 1, temp + 1); + data->word = (temp[1] << 8) | temp[0]; break; - case 0x0007: - err_str = "unknown error"; + + case ACPI_SBS_BLOCK_DATA: + len = 0; + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_BCNT, &len); + len = min_t(u8, len, 32); + for (i = 0; i < len; i++) + acpi_ec_sbs_read(sbs, ACPI_EC_SMB_DATA + i, + data->block + i + 1); + data->block[0] = len; break; default: - err_str = "unrecognized error"; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "unsupported transaction %d", size)); + return (-1); } - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "%s: ret %i, err %i\n", err_str, result, err_number)); + + return (0); } static int -acpi_sbs_smbus_read_word(struct acpi_ec_smbus *smbus, int addr, int func, - u16 * word, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_read_word(struct acpi_sbs *sbs, int addr, int func, u16 * word) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } - - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_READ, func, - I2C_SMBUS_WORD_DATA, &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo->smbus_xfer() failed\n", - i)); - if (err_handler) { - err_handler(smbus); - } - } else { - *word = data.word; - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_READ, func, + ACPI_SBS_WORD_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); + } else { + *word = data.word; } return result; } static int -acpi_sbs_smbus_read_str(struct acpi_ec_smbus *smbus, int addr, int func, - char *str, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_read_str(struct acpi_sbs *sbs, int addr, int func, char *str) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_READ, func, - I2C_SMBUS_BLOCK_DATA, - &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo->smbus_xfer() failed\n", - i)); - if (err_handler) { - err_handler(smbus); - } - } else { - strncpy(str, (const char *)data.block + 1, - data.block[0]); - str[data.block[0]] = 0; - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_READ, func, + ACPI_SBS_BLOCK_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); + } else { + strncpy(str, (const char *)data.block + 1, data.block[0]); + str[data.block[0]] = 0; } return result; } static int -acpi_sbs_smbus_write_word(struct acpi_ec_smbus *smbus, int addr, int func, - int word, - void (*err_handler) (struct acpi_ec_smbus * smbus)) +acpi_sbs_write_word(struct acpi_sbs *sbs, int addr, int func, int word) { - union i2c_smbus_data data; + union sbs_rw_data data; int result = 0; - int i; - - if (err_handler == NULL) { - err_handler = acpi_battery_smbus_err_handler; - } data.word = word; - for (i = 0; i < MAX_SMBUS_ERR; i++) { - result = - smbus->adapter.algo->smbus_xfer(&smbus->adapter, addr, 0, - I2C_SMBUS_WRITE, func, - I2C_SMBUS_WORD_DATA, &data); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "try %i: smbus->adapter.algo" - "->smbus_xfer() failed\n", i)); - if (err_handler) { - err_handler(smbus); - } - } else { - break; - } + result = acpi_ec_sbs_access(sbs, addr, + ACPI_SBS_SMBUS_WRITE, func, + ACPI_SBS_WORD_DATA, &data); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ec_sbs_access() failed")); } return result; } +static int sbs_zombie(struct acpi_sbs *sbs) +{ + return (sbs->zombie); +} + +static int sbs_mutex_lock(struct acpi_sbs *sbs) +{ + if (sbs_zombie(sbs)) { + return -ENODEV; + } + mutex_lock(&sbs->mutex); + return 0; +} + +static void sbs_mutex_unlock(struct acpi_sbs *sbs) +{ + mutex_unlock(&sbs->mutex); +} + /* -------------------------------------------------------------------------- Smart Battery System Management -------------------------------------------------------------------------- */ -/* Smart Battery */ +static int acpi_check_update_proc(struct acpi_sbs *sbs) +{ + acpi_status status = AE_OK; + + if (update_time == 0) { + sbs->update_proc_flg = 0; + return 0; + } + if (sbs->update_proc_flg == 0) { + status = acpi_os_execute(OSL_GPE_HANDLER, + acpi_sbs_update_time, sbs); + if (status != AE_OK) { + ACPI_EXCEPTION((AE_INFO, status, + "acpi_os_execute() failed")); + return 1; + } + sbs->update_proc_flg = 1; + } + return 0; +} static int acpi_sbs_generate_event(struct acpi_device *device, int event, int state, char *bid, char *class) @@ -366,12 +450,11 @@ static int acpi_battery_get_present(stru int result = 0; int is_present = 0; - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SBSM_SMBUS_ADDR, 0x01, - &state, NULL); + result = acpi_sbs_read_word(battery->sbs, + ACPI_SBSM_SMBUS_ADDR, 0x01, &state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); } if (!result) { is_present = (state & 0x000f) & (1 << battery->id); @@ -381,45 +464,33 @@ static int acpi_battery_get_present(stru return result; } -static int acpi_battery_is_present(struct acpi_battery *battery) -{ - return (battery->battery_present); -} - -static int acpi_ac_is_present(struct acpi_sbs *sbs) -{ - return (sbs->ac_present); -} - static int acpi_battery_select(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 state; int foo; - if (battery->sbs->sbsm_present) { + if (sbs->sbsm_present) { /* Take special care not to knobble other nibbles of * state (aka selector_state), since * it causes charging to halt on SBSELs */ result = - acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01, - &state, NULL); + acpi_sbs_read_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x01, &state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } foo = (state & 0x0fff) | (1 << (battery->id + 12)); result = - acpi_sbs_smbus_write_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x01, - foo, NULL); + acpi_sbs_write_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x01, foo); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } } @@ -430,15 +501,14 @@ static int acpi_battery_select(struct ac static int acpi_sbsm_get_info(struct acpi_sbs *sbs) { - struct acpi_ec_smbus *smbus = sbs->smbus; int result = 0; s16 battery_system_info; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SBSM_SMBUS_ADDR, 0x04, - &battery_system_info, NULL); + result = acpi_sbs_read_word(sbs, ACPI_SBSM_SMBUS_ADDR, 0x04, + &battery_system_info); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -451,53 +521,50 @@ static int acpi_sbsm_get_info(struct acp static int acpi_battery_get_info(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; s16 specification_info; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x03, + &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } battery->info.capacity_mode = (battery_mode & 0x8000) >> 15; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x10, - &battery->info.full_charge_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x10, + &battery->info.full_charge_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x18, - &battery->info.design_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x18, + &battery->info.design_capacity); if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x19, - &battery->info.design_voltage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x19, + &battery->info.design_voltage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1a, - &specification_info, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x1a, + &specification_info); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -529,37 +596,35 @@ static int acpi_battery_get_info(struct battery->info.ipscale = 1; } - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x1c, - &battery->info.serial_number, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x1c, + &battery->info.serial_number); if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x20, - battery->info.manufacturer_name, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x20, + battery->info.manufacturer_name); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x21, - battery->info.device_name, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x21, + battery->info.device_name); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } - result = acpi_sbs_smbus_read_str(smbus, ACPI_SB_SMBUS_ADDR, 0x22, - battery->info.device_chemistry, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_str(sbs, ACPI_SB_SMBUS_ADDR, 0x22, + battery->info.device_chemistry); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_str() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_str() failed")); goto end; } @@ -567,103 +632,60 @@ static int acpi_battery_get_info(struct return result; } -static void acpi_update_delay(struct acpi_sbs *sbs) -{ - if (sbs->zombie) { - return; - } - if (sbs->update_time2 > 0) { - msleep(sbs->update_time2 * 1000); - } -} - static int acpi_battery_get_state(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x09, - &battery->state.voltage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x09, + &battery->state.voltage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0a, - &battery->state.amperage, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x0a, + &battery->state.amperage); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x0f, - &battery->state.remaining_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x0f, + &battery->state.remaining_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x12, - &battery->state.average_time_to_empty, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x16, + &battery->state.battery_state); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x13, - &battery->state.average_time_to_full, - &acpi_battery_smbus_err_handler); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); - goto end; - } - - acpi_update_delay(battery->sbs); - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x16, - &battery->state.battery_status, - &acpi_battery_smbus_err_handler); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); - goto end; - } - - acpi_update_delay(battery->sbs); - end: return result; } static int acpi_battery_get_alarm(struct acpi_battery *battery) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - &battery->alarm.remaining_capacity, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, + &battery->alarm.remaining_capacity); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - acpi_update_delay(battery->sbs); - end: return result; @@ -672,15 +694,15 @@ static int acpi_battery_get_alarm(struct static int acpi_battery_set_alarm(struct acpi_battery *battery, unsigned long alarm) { - struct acpi_ec_smbus *smbus = battery->sbs->smbus; + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; int foo; result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } @@ -688,33 +710,29 @@ static int acpi_battery_set_alarm(struct if (alarm > 0) { result = - acpi_sbs_smbus_read_word(smbus, ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, - &acpi_battery_smbus_err_handler); + acpi_sbs_read_word(sbs, ACPI_SB_SMBUS_ADDR, 0x03, + &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } result = - acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - battery_mode & 0xbfff, - &acpi_battery_smbus_err_handler); + acpi_sbs_write_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, + battery_mode & 0xbfff); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } } foo = alarm / (battery->info.capacity_mode ? 10 : 1); - result = acpi_sbs_smbus_write_word(smbus, ACPI_SB_SMBUS_ADDR, 0x01, - foo, - &acpi_battery_smbus_err_handler); + result = acpi_sbs_write_word(sbs, ACPI_SB_SMBUS_ADDR, 0x01, foo); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } @@ -725,6 +743,7 @@ static int acpi_battery_set_alarm(struct static int acpi_battery_set_mode(struct acpi_battery *battery) { + struct acpi_sbs *sbs = battery->sbs; int result = 0; s16 battery_mode; @@ -732,12 +751,11 @@ static int acpi_battery_set_mode(struct goto end; } - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, NULL); + result = acpi_sbs_read_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -746,21 +764,19 @@ static int acpi_battery_set_mode(struct } else { battery_mode |= 0x8000; } - result = acpi_sbs_smbus_write_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - battery_mode, NULL); + result = acpi_sbs_write_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_write_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_write_word() failed")); goto end; } - result = acpi_sbs_smbus_read_word(battery->sbs->smbus, - ACPI_SB_SMBUS_ADDR, 0x03, - &battery_mode, NULL); + result = acpi_sbs_read_word(sbs, + ACPI_SB_SMBUS_ADDR, 0x03, &battery_mode); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } @@ -774,36 +790,36 @@ static int acpi_battery_init(struct acpi result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } result = acpi_battery_set_mode(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_set_mode() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_set_mode() failed")); goto end; } result = acpi_battery_get_info(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_info() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_info() failed")); goto end; } result = acpi_battery_get_state(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_state() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_state() failed")); goto end; } result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() failed")); goto end; } @@ -813,20 +829,19 @@ static int acpi_battery_init(struct acpi static int acpi_ac_get_present(struct acpi_sbs *sbs) { - struct acpi_ec_smbus *smbus = sbs->smbus; int result = 0; s16 charger_status; - result = acpi_sbs_smbus_read_word(smbus, ACPI_SBC_SMBUS_ADDR, 0x13, - &charger_status, NULL); + result = acpi_sbs_read_word(sbs, ACPI_SBC_SMBUS_ADDR, 0x13, + &charger_status); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_smbus_read_word() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_read_word() failed")); goto end; } - sbs->ac_present = (charger_status & 0x8000) >> 15; + sbs->ac.ac_present = (charger_status & 0x8000) >> 15; end: @@ -852,8 +867,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_ if (!*dir) { *dir = proc_mkdir(dir_name, parent_dir); if (!*dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "proc_mkdir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "proc_mkdir() failed")); return -ENODEV; } (*dir)->owner = THIS_MODULE; @@ -863,8 +878,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_ if (info_fops) { entry = create_proc_entry(ACPI_SBS_FILE_INFO, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = info_fops; entry->data = data; @@ -876,8 +891,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_ if (state_fops) { entry = create_proc_entry(ACPI_SBS_FILE_STATE, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = state_fops; entry->data = data; @@ -889,8 +904,8 @@ acpi_sbs_generic_add_fs(struct proc_dir_ if (alarm_fops) { entry = create_proc_entry(ACPI_SBS_FILE_ALARM, S_IRUGO, *dir); if (!entry) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "create_proc_entry() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "create_proc_entry() failed")); } else { entry->proc_fops = alarm_fops; entry->data = data; @@ -923,24 +938,27 @@ static struct proc_dir_entry *acpi_batte static int acpi_battery_read_info(struct seq_file *seq, void *offset) { struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int cscale; int result = 0; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_INFO); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_INFO); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (acpi_battery_is_present(battery)) { + if (battery->battery_present) { seq_printf(seq, "present: yes\n"); } else { seq_printf(seq, "present: no\n"); @@ -952,13 +970,13 @@ static int acpi_battery_read_info(struct } else { cscale = battery->info.ipscale; } - seq_printf(seq, "design capacity: %i%s", + seq_printf(seq, "design capacity: %i%s\n", battery->info.design_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); - seq_printf(seq, "last full capacity: %i%s", + seq_printf(seq, "last full capacity: %i%s\n", battery->info.full_charge_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); seq_printf(seq, "battery technology: rechargeable\n"); @@ -984,7 +1002,7 @@ static int acpi_battery_read_info(struct end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -996,26 +1014,29 @@ static int acpi_battery_info_open_fs(str static int acpi_battery_read_state(struct seq_file *seq, void *offset) { - struct acpi_battery *battery = (struct acpi_battery *)seq->private; + struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int result = 0; int cscale; int foo; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_STATE); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_STATE); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (acpi_battery_is_present(battery)) { + if (battery->battery_present) { seq_printf(seq, "present: yes\n"); } else { seq_printf(seq, "present: no\n"); @@ -1028,7 +1049,7 @@ static int acpi_battery_read_state(struc cscale = battery->info.ipscale; } - if (battery->state.battery_status & 0x0010) { + if (battery->state.battery_state & 0x0010) { seq_printf(seq, "capacity state: critical\n"); } else { seq_printf(seq, "capacity state: ok\n"); @@ -1052,16 +1073,16 @@ static int acpi_battery_read_state(struc battery->info.capacity_mode ? "mW" : "mA"); } - seq_printf(seq, "remaining capacity: %i%s", + seq_printf(seq, "remaining capacity: %i%s\n", battery->state.remaining_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); seq_printf(seq, "present voltage: %i mV\n", battery->state.voltage * battery->info.vscale); end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -1074,24 +1095,27 @@ static int acpi_battery_state_open_fs(st static int acpi_battery_read_alarm(struct seq_file *seq, void *offset) { struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; int result = 0; int cscale; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(battery->sbs, DATA_TYPE_ALARM); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, battery->id, DATA_TYPE_ALARM); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } - if (!acpi_battery_is_present(battery)) { + if (!battery->battery_present) { seq_printf(seq, "present: no\n"); goto end; } @@ -1104,16 +1128,16 @@ static int acpi_battery_read_alarm(struc seq_printf(seq, "alarm: "); if (battery->alarm.remaining_capacity) { - seq_printf(seq, "%i%s", + seq_printf(seq, "%i%s\n", battery->alarm.remaining_capacity * cscale, - battery->info.capacity_mode ? "0 mWh\n" : " mAh\n"); + battery->info.capacity_mode ? "0 mWh" : " mAh"); } else { seq_printf(seq, "disabled\n"); } end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); return result; } @@ -1124,16 +1148,19 @@ acpi_battery_write_alarm(struct file *fi { struct seq_file *seq = file->private_data; struct acpi_battery *battery = seq->private; + struct acpi_sbs *sbs = battery->sbs; char alarm_string[12] = { '\0' }; int result, old_alarm, new_alarm; - if (battery->sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); + result = acpi_check_update_proc(sbs); + if (result) + goto end; - if (!acpi_battery_is_present(battery)) { + if (!battery->battery_present) { result = -ENODEV; goto end; } @@ -1155,21 +1182,21 @@ acpi_battery_write_alarm(struct file *fi result = acpi_battery_set_alarm(battery, new_alarm); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_set_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_set_alarm() failed")); acpi_battery_set_alarm(battery, old_alarm); goto end; } result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() failed")); acpi_battery_set_alarm(battery, old_alarm); goto end; } end: - up(&sbs_sem); + sbs_mutex_unlock(sbs); if (result) { return result; @@ -1217,24 +1244,22 @@ static int acpi_ac_read_state(struct seq struct acpi_sbs *sbs = seq->private; int result; - if (sbs->zombie) { + if (sbs_mutex_lock(sbs)) { return -ENODEV; } - down(&sbs_sem); - - if (update_mode == REQUEST_UPDATE_MODE) { - result = acpi_sbs_update_run(sbs, DATA_TYPE_AC_STATE); + if (update_time == 0) { + result = acpi_sbs_update_run(sbs, -1, DATA_TYPE_AC_STATE); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } } seq_printf(seq, "state: %s\n", - sbs->ac_present ? "on-line" : "off-line"); + sbs->ac.ac_present ? "on-line" : "off-line"); - up(&sbs_sem); + sbs_mutex_unlock(sbs); return 0; } @@ -1275,25 +1300,25 @@ static int acpi_battery_add(struct acpi_ result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); goto end; } result = acpi_battery_get_present(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_present() failed")); goto end; } - is_present = acpi_battery_is_present(battery); + is_present = battery->battery_present; if (is_present) { result = acpi_battery_init(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_init() failed")); goto end; } battery->init_state = 1; @@ -1308,12 +1333,16 @@ static int acpi_battery_add(struct acpi_ &acpi_battery_state_fops, &acpi_battery_alarm_fops, battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generic_add_fs() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generic_add_fs() failed")); goto end; } battery->alive = 1; + printk(KERN_INFO PREFIX "%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), dir_name, + sbs->battery->battery_present ? "present" : "absent"); + end: return result; } @@ -1333,8 +1362,8 @@ static int acpi_ac_add(struct acpi_sbs * result = acpi_ac_get_present(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_ac_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ac_get_present() failed")); goto end; } @@ -1343,11 +1372,15 @@ static int acpi_ac_add(struct acpi_sbs * ACPI_AC_DIR_NAME, NULL, &acpi_ac_state_fops, NULL, sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generic_add_fs() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generic_add_fs() failed")); goto end; } + printk(KERN_INFO PREFIX "%s [%s]: AC Adapter [%s] (%s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), + ACPI_AC_DIR_NAME, sbs->ac.ac_present ? "on-line" : "off-line"); + end: return result; @@ -1361,45 +1394,85 @@ static void acpi_ac_remove(struct acpi_s } } -static void acpi_sbs_update_queue_run(unsigned long data) +static void acpi_sbs_update_time_run(unsigned long data) { - acpi_os_execute(OSL_GPE_HANDLER, acpi_sbs_update_queue, (void *)data); + acpi_os_execute(OSL_GPE_HANDLER, acpi_sbs_update_time, (void *)data); } -static int acpi_sbs_update_run(struct acpi_sbs *sbs, int data_type) +static int acpi_sbs_update_run(struct acpi_sbs *sbs, int id, int data_type) { struct acpi_battery *battery; - int result = 0; - int old_ac_present; - int old_battery_present; - int new_ac_present; - int new_battery_present; - int id; + int result = 0, cnt; + int old_ac_present = -1; + int old_battery_present = -1; + int new_ac_present = -1; + int new_battery_present = -1; + int id_min = 0, id_max = MAX_SBS_BAT - 1; char dir_name[32]; - int do_battery_init, do_ac_init; - s16 old_remaining_capacity; + int do_battery_init = 0, do_ac_init = 0; + int old_remaining_capacity = 0; + int update_ac = 1, update_battery = 1; + int up_tm = update_time; - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - old_ac_present = acpi_ac_is_present(sbs); + if (id >= 0) { + id_min = id_max = id; + } + + if (data_type == DATA_TYPE_COMMON && up_tm > 0) { + cnt = up_tm / (up_tm > UPDATE_DELAY ? UPDATE_DELAY : up_tm); + if (sbs->run_cnt % cnt != 0) { + update_battery = 0; + } + } + + sbs->run_cnt++; + + if (!update_ac && !update_battery) { + goto end; + } + + old_ac_present = sbs->ac.ac_present; result = acpi_ac_get_present(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_ac_get_present() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_ac_get_present() failed")); } - new_ac_present = acpi_ac_is_present(sbs); + new_ac_present = sbs->ac.ac_present; do_ac_init = (old_ac_present != new_ac_present); + if (sbs->run_cnt == 1 && data_type == DATA_TYPE_COMMON) { + do_ac_init = 1; + } - if (data_type == DATA_TYPE_AC_STATE) { + if (do_ac_init) { + result = acpi_sbs_generate_event(sbs->device, + ACPI_SBS_AC_NOTIFY_STATUS, + new_ac_present, + ACPI_AC_DIR_NAME, + ACPI_AC_CLASS); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generate_event() failed")); + } + } + + if (data_type == DATA_TYPE_COMMON) { + if (!do_ac_init && !update_battery) { + goto end; + } + } + + if (data_type == DATA_TYPE_AC_STATE && !do_ac_init) { goto end; } - for (id = 0; id < MAX_SBS_BAT; id++) { + for (id = id_min; id <= id_max; id++) { battery = &sbs->battery[id]; if (battery->alive == 0) { continue; @@ -1407,94 +1480,92 @@ static int acpi_sbs_update_run(struct ac old_remaining_capacity = battery->state.remaining_capacity; - old_battery_present = acpi_battery_is_present(battery); + old_battery_present = battery->battery_present; result = acpi_battery_select(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_select() failed\n")); - } - if (sbs->zombie) { - goto end; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_select() failed")); } result = acpi_battery_get_present(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_present() failed\n")); - } - if (sbs->zombie) { - goto end; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_present() failed")); } - new_battery_present = acpi_battery_is_present(battery); + new_battery_present = battery->battery_present; do_battery_init = ((old_battery_present != new_battery_present) && new_battery_present); - - if (sbs->zombie) { + if (!new_battery_present) + goto event; + if (do_ac_init || do_battery_init) { + result = acpi_battery_init(battery); + if (result) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_init() " + "failed")); + } + } + if (sbs_zombie(sbs)) { goto end; } - if (do_ac_init || do_battery_init || - update_info_mode || sbs->update_info_mode) { - if (sbs->update_info_mode) { - sbs->update_info_mode = 0; - } else { - sbs->update_info_mode = 1; - } - result = acpi_battery_init(battery); + + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_INFO) + && new_battery_present) { + result = acpi_battery_get_info(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_init() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_info() failed")); } } if (data_type == DATA_TYPE_INFO) { continue; } - - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - if (new_battery_present) { - result = acpi_battery_get_alarm(battery); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_alarm() " - "failed\n")); - } - if (data_type == DATA_TYPE_ALARM) { - continue; - } + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_STATE) + && new_battery_present) { result = acpi_battery_get_state(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_get_state() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_state() failed")); } } - if (sbs->zombie) { - goto end; + if (data_type == DATA_TYPE_STATE) { + goto event; } - if (data_type != DATA_TYPE_COMMON) { - continue; + if (sbs_zombie(sbs)) { + goto end; } - if (old_battery_present != new_battery_present) { - sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id); - result = acpi_sbs_generate_event(sbs->device, - ACPI_SBS_BATTERY_NOTIFY_STATUS, - new_battery_present, - dir_name, - ACPI_BATTERY_CLASS); + if ((data_type == DATA_TYPE_COMMON + || data_type == DATA_TYPE_ALARM) + && new_battery_present) { + result = acpi_battery_get_alarm(battery); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_get_alarm() " + "failed")); } } - if (old_remaining_capacity != battery->state.remaining_capacity) { + if (data_type == DATA_TYPE_ALARM) { + continue; + } + if (sbs_zombie(sbs)) { + goto end; + } + + event: + + if (old_battery_present != new_battery_present || do_ac_init || + old_remaining_capacity != + battery->state.remaining_capacity) { sprintf(dir_name, ACPI_BATTERY_DIR_NAME, id); result = acpi_sbs_generate_event(sbs->device, ACPI_SBS_BATTERY_NOTIFY_STATUS, @@ -1502,138 +1573,120 @@ static int acpi_sbs_update_run(struct ac dir_name, ACPI_BATTERY_CLASS); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_generate_event() " + "failed")); } } - - } - if (sbs->zombie) { - goto end; - } - if (data_type != DATA_TYPE_COMMON) { - goto end; - } - - if (old_ac_present != new_ac_present) { - result = acpi_sbs_generate_event(sbs->device, - ACPI_SBS_AC_NOTIFY_STATUS, - new_ac_present, - ACPI_AC_DIR_NAME, - ACPI_AC_CLASS); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_generate_event() failed\n")); - } } end: + return result; } -static void acpi_sbs_update_queue(void *data) +static void acpi_sbs_update_time(void *data) { struct acpi_sbs *sbs = data; unsigned long delay = -1; int result; + unsigned int up_tm = update_time; - if (sbs->zombie) { - goto end; - } + if (sbs_mutex_lock(sbs)) + return; - result = acpi_sbs_update_run(sbs, DATA_TYPE_COMMON); + result = acpi_sbs_update_run(sbs, -1, DATA_TYPE_COMMON); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_update_run() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_update_run() failed")); } - if (sbs->zombie) { + if (sbs_zombie(sbs)) { goto end; } - if (update_mode == REQUEST_UPDATE_MODE) { - goto end; + if (!up_tm) { + if (timer_pending(&sbs->update_timer)) + del_timer(&sbs->update_timer); + } else { + delay = (up_tm > UPDATE_DELAY ? UPDATE_DELAY : up_tm); + delay = jiffies + HZ * delay; + if (timer_pending(&sbs->update_timer)) { + mod_timer(&sbs->update_timer, delay); + } else { + sbs->update_timer.data = (unsigned long)data; + sbs->update_timer.function = acpi_sbs_update_time_run; + sbs->update_timer.expires = delay; + add_timer(&sbs->update_timer); + } } - delay = jiffies + HZ * update_time; - sbs->update_timer.data = (unsigned long)data; - sbs->update_timer.function = acpi_sbs_update_queue_run; - sbs->update_timer.expires = delay; - add_timer(&sbs->update_timer); end: - ; + + sbs_mutex_unlock(sbs); } static int acpi_sbs_add(struct acpi_device *device) { struct acpi_sbs *sbs = NULL; - struct acpi_ec_hc *ec_hc = NULL; - int result, remove_result = 0; + int result = 0, remove_result = 0; unsigned long sbs_obj; - int id, cnt; + int id; acpi_status status = AE_OK; + unsigned long val; + + status = + acpi_evaluate_integer(device->parent->handle, "_EC", NULL, &val); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "Error obtaining _EC")); + return -EIO; + } sbs = kzalloc(sizeof(struct acpi_sbs), GFP_KERNEL); if (!sbs) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "kmalloc() failed\n")); - return -ENOMEM; + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "kzalloc() failed")); + result = -ENOMEM; + goto end; } - cnt = 0; - while (cnt < 10) { - cnt++; - ec_hc = acpi_get_ec_hc(device); - if (ec_hc) { - break; - } - msleep(1000); - } + mutex_init(&sbs->mutex); - if (!ec_hc) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_get_ec_hc() failed: " - "NO driver found for EC HC SMBus\n")); - result = -ENODEV; - goto end; - } + sbs_mutex_lock(sbs); + sbs->base = (val & 0xff00ull) >> 8; sbs->device = device; - sbs->smbus = ec_hc->smbus; strcpy(acpi_device_name(device), ACPI_SBS_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_SBS_CLASS); acpi_driver_data(device) = sbs; - sbs->update_time = 0; - sbs->update_time2 = 0; - result = acpi_ac_add(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_ac_add() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, "acpi_ac_add() failed")); goto end; } - result = acpi_evaluate_integer(device->handle, "_SBS", NULL, &sbs_obj); - if (ACPI_FAILURE(result)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_evaluate_integer() failed\n")); + status = acpi_evaluate_integer(device->handle, "_SBS", NULL, &sbs_obj); + if (status) { + ACPI_EXCEPTION((AE_INFO, status, + "acpi_evaluate_integer() failed")); result = -EIO; goto end; } - if (sbs_obj > 0) { result = acpi_sbsm_get_info(sbs); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbsm_get_info() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbsm_get_info() failed")); goto end; } sbs->sbsm_present = 1; } + if (sbs->sbsm_present == 0) { result = acpi_battery_add(sbs, 0); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_add() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_add() failed")); goto end; } } else { @@ -1641,9 +1694,8 @@ static int acpi_sbs_add(struct acpi_devi if ((sbs->sbsm_batteries_supported & (1 << id))) { result = acpi_battery_add(sbs, id); if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_battery_add() " - "failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_battery_add() failed")); goto end; } } @@ -1653,33 +1705,26 @@ static int acpi_sbs_add(struct acpi_devi sbs->handle = device->handle; init_timer(&sbs->update_timer); - if (update_mode == QUEUE_UPDATE_MODE) { - status = acpi_os_execute(OSL_GPE_HANDLER, - acpi_sbs_update_queue, sbs); - if (status != AE_OK) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_os_execute() failed\n")); - } - } - sbs->update_time = update_time; - sbs->update_time2 = update_time2; - - printk(KERN_INFO PREFIX "%s [%s]\n", - acpi_device_name(device), acpi_device_bid(device)); + result = acpi_check_update_proc(sbs); + if (result) + goto end; end: + + sbs_mutex_unlock(sbs); + if (result) { remove_result = acpi_sbs_remove(device, 0); if (remove_result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_sbs_remove() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_sbs_remove() failed")); } } return result; } -int acpi_sbs_remove(struct acpi_device *device, int type) +static int acpi_sbs_remove(struct acpi_device *device, int type) { struct acpi_sbs *sbs; int id; @@ -1688,15 +1733,14 @@ int acpi_sbs_remove(struct acpi_device * return -EINVAL; } - sbs = (struct acpi_sbs *)acpi_driver_data(device); - + sbs = acpi_driver_data(device); if (!sbs) { return -EINVAL; } + sbs_mutex_lock(sbs); + sbs->zombie = 1; - sbs->update_time = 0; - sbs->update_time2 = 0; del_timer_sync(&sbs->update_timer); acpi_os_wait_events_complete(NULL); del_timer_sync(&sbs->update_timer); @@ -1707,11 +1751,41 @@ int acpi_sbs_remove(struct acpi_device * acpi_ac_remove(sbs); + sbs_mutex_unlock(sbs); + + mutex_destroy(&sbs->mutex); + kfree(sbs); return 0; } +static void acpi_sbs_rmdirs(void) +{ + if (acpi_ac_dir) { + acpi_unlock_ac_dir(acpi_ac_dir); + acpi_ac_dir = NULL; + } + if (acpi_battery_dir) { + acpi_unlock_battery_dir(acpi_battery_dir); + acpi_battery_dir = NULL; + } +} + +static int acpi_sbs_resume(struct acpi_device *device) +{ + struct acpi_sbs *sbs; + + if (!device) + return -EINVAL; + + sbs = device->driver_data; + + sbs->run_cnt = 0; + + return 0; +} + static int __init acpi_sbs_init(void) { int result = 0; @@ -1719,35 +1793,34 @@ static int __init acpi_sbs_init(void) if (acpi_disabled) return -ENODEV; - init_MUTEX(&sbs_sem); - if (capacity_mode != DEF_CAPACITY_UNIT && capacity_mode != MAH_CAPACITY_UNIT && capacity_mode != MWH_CAPACITY_UNIT) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "acpi_sbs_init: " - "invalid capacity_mode = %d\n", - capacity_mode)); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "invalid capacity_mode = %d", capacity_mode)); return -EINVAL; } acpi_ac_dir = acpi_lock_ac_dir(); if (!acpi_ac_dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_lock_ac_dir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_lock_ac_dir() failed")); return -ENODEV; } acpi_battery_dir = acpi_lock_battery_dir(); if (!acpi_battery_dir) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_lock_battery_dir() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_lock_battery_dir() failed")); + acpi_sbs_rmdirs(); return -ENODEV; } result = acpi_bus_register_driver(&acpi_sbs_driver); if (result < 0) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "acpi_bus_register_driver() failed\n")); + ACPI_EXCEPTION((AE_INFO, AE_ERROR, + "acpi_bus_register_driver() failed")); + acpi_sbs_rmdirs(); return -ENODEV; } @@ -1756,13 +1829,9 @@ static int __init acpi_sbs_init(void) static void __exit acpi_sbs_exit(void) { - acpi_bus_unregister_driver(&acpi_sbs_driver); - acpi_unlock_ac_dir(acpi_ac_dir); - acpi_ac_dir = NULL; - acpi_unlock_battery_dir(acpi_battery_dir); - acpi_battery_dir = NULL; + acpi_sbs_rmdirs(); return; } diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig new file mode 100644 index 0000000..b9ac217 --- /dev/null +++ b/drivers/cpuidle/Kconfig @@ -0,0 +1,28 @@ +menu "CPU idle PM support" + +config CPU_IDLE + bool "CPU idle PM support" + help + CPU idle is a generic framework for supporting software-controlled + idle processor power management. It includes modular cross-platform + governors that can be swapped during runtime. + + If you're using a mobile platform that supports CPU idle PM (e.g. + an ACPI-capable notebook), you should say Y here. + +if CPU_IDLE + +comment "Governors" + +config CPU_IDLE_GOV_LADDER + tristate "'ladder' governor" + depends on CPU_IDLE + default y + help + This cpuidle governor promotes and demotes through the supported idle + states using residency time and bus master activity as metrics. This + algorithm was originally introduced in the old ACPI processor driver. + +endif # CPU_IDLE + +endmenu diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile new file mode 100644 index 0000000..5634f88 --- /dev/null +++ b/drivers/cpuidle/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for cpuidle. +# + +obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c new file mode 100644 index 0000000..4d2071e --- /dev/null +++ b/drivers/cpuidle/cpuidle.c @@ -0,0 +1,283 @@ +/* + * cpuidle.c - core cpuidle infrastructure + * + * (C) 2006-2007 Venkatesh Pallipadi + * Shaohua Li + * Adam Belay + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cpuidle.h" + +DEFINE_PER_CPU(struct cpuidle_device, cpuidle_devices); +EXPORT_PER_CPU_SYMBOL_GPL(cpuidle_devices); + +DEFINE_MUTEX(cpuidle_lock); +LIST_HEAD(cpuidle_detected_devices); +static void (*pm_idle_old)(void); + + +/** + * cpuidle_idle_call - the main idle loop + * + * NOTE: no locks or semaphores should be used here + * FIXME: DYNTICKS handling + */ +static void cpuidle_idle_call(void) +{ + struct cpuidle_device *dev = &__get_cpu_var(cpuidle_devices); + + struct cpuidle_state *target_state; + int next_state; + + /* check if the device is ready */ + if (dev->status != CPUIDLE_STATUS_DOIDLE) { + if (pm_idle_old) + pm_idle_old(); + return; + } + + if (cpuidle_curr_governor->prepare_idle) + cpuidle_curr_governor->prepare_idle(dev); + + while(!need_resched()) { + next_state = cpuidle_curr_governor->select_state(dev); + if (need_resched()) + break; + + target_state = &dev->states[next_state]; + + dev->last_residency = target_state->enter(dev, target_state); + dev->last_state = target_state; + target_state->time += dev->last_residency; + target_state->usage++; + + if (dev->status != CPUIDLE_STATUS_DOIDLE) + break; + } +} + +/** + * cpuidle_install_idle_handler - installs the cpuidle idle loop handler + */ +void cpuidle_install_idle_handler(void) +{ + if (pm_idle != cpuidle_idle_call) { + /* Make sure all changes finished before we switch to new idle */ + smp_wmb(); + pm_idle = cpuidle_idle_call; + } +} + +/** + * cpuidle_uninstall_idle_handler - uninstalls the cpuidle idle loop handler + */ +void cpuidle_uninstall_idle_handler(void) +{ + if (pm_idle != pm_idle_old) { + pm_idle = pm_idle_old; + cpu_idle_wait(); + } +} + +/** + * cpuidle_rescan_device - prepares for a new state configuration + * @dev: the target device + * + * Must be called with cpuidle_lock aquired. + */ +void cpuidle_rescan_device(struct cpuidle_device *dev) +{ + int i; + + if (cpuidle_curr_governor->scan) + cpuidle_curr_governor->scan(dev); + + for (i = 0; i < dev->state_count; i++) { + dev->states[i].usage = 0; + dev->states[i].time = 0; + } +} + +/** + * cpuidle_add_device - attaches the driver to a CPU instance + * @sys_dev: the system device (driver model CPU representation) + */ +static int cpuidle_add_device(struct sys_device *sys_dev) +{ + int cpu = sys_dev->id; + struct cpuidle_device *dev; + + dev = &per_cpu(cpuidle_devices, cpu); + + mutex_lock(&cpuidle_lock); + if (cpu_is_offline(cpu)) { + mutex_unlock(&cpuidle_lock); + return 0; + } + + if (dev->status & CPUIDLE_STATUS_DETECTED) { + mutex_unlock(&cpuidle_lock); + return 0; + } + dev->status |= CPUIDLE_STATUS_DETECTED; + list_add(&dev->device_list, &cpuidle_detected_devices); + cpuidle_add_sysfs(sys_dev); + if (cpuidle_curr_driver) + cpuidle_attach_driver(dev); + if (cpuidle_curr_governor) + cpuidle_attach_governor(dev); + if (cpuidle_device_can_idle(dev)) + cpuidle_install_idle_handler(); + mutex_unlock(&cpuidle_lock); + + return 0; +} + +/** + * __cpuidle_remove_device - detaches the driver from a CPU instance + * @sys_dev: the system device (driver model CPU representation) + * + * Must be called with cpuidle_lock aquired. + */ +static int __cpuidle_remove_device(struct sys_device *sys_dev) +{ + struct cpuidle_device *dev; + + dev = &per_cpu(cpuidle_devices, sys_dev->id); + + if (!(dev->status & CPUIDLE_STATUS_DETECTED)) { + return 0; + } + dev->status &= ~CPUIDLE_STATUS_DETECTED; + /* NOTE: we don't wait because the cpu is already offline */ + if (cpuidle_curr_governor) + cpuidle_detach_governor(dev); + if (cpuidle_curr_driver) + cpuidle_detach_driver(dev); + cpuidle_remove_sysfs(sys_dev); + list_del(&dev->device_list); + + return 0; +} + +/** + * cpuidle_remove_device - detaches the driver from a CPU instance + * @sys_dev: the system device (driver model CPU representation) + */ +static int cpuidle_remove_device(struct sys_device *sys_dev) +{ + int ret; + mutex_lock(&cpuidle_lock); + ret = __cpuidle_remove_device(sys_dev); + mutex_unlock(&cpuidle_lock); + + return ret; +} + +static struct sysdev_driver cpuidle_sysdev_driver = { + .add = cpuidle_add_device, + .remove = cpuidle_remove_device, +}; + +static int cpuidle_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + struct sys_device *sys_dev; + + sys_dev = get_cpu_sysdev((unsigned long)hcpu); + + switch (action) { + case CPU_ONLINE: + cpuidle_add_device(sys_dev); + break; + case CPU_DOWN_PREPARE: + mutex_lock(&cpuidle_lock); + break; + case CPU_DEAD: + __cpuidle_remove_device(sys_dev); + mutex_unlock(&cpuidle_lock); + break; + case CPU_DOWN_FAILED: + mutex_unlock(&cpuidle_lock); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata cpuidle_cpu_notifier = +{ + .notifier_call = cpuidle_cpu_callback, +}; + +#ifdef CONFIG_SMP + +static void smp_callback(void *v) +{ + /* we already woke the CPU up, nothing more to do */ +} + +/* + * This function gets called when a part of the kernel has a new latency + * requirement. This means we need to get all processors out of their C-state, + * and then recalculate a new suitable C-state. Just do a cross-cpu IPI; that + * wakes them all right up. + */ +static int cpuidle_latency_notify(struct notifier_block *b, + unsigned long l, void *v) +{ + smp_call_function(smp_callback, NULL, 0, 1); + return NOTIFY_OK; +} + +static struct notifier_block cpuidle_latency_notifier = { + .notifier_call = cpuidle_latency_notify, +}; + +#define latency_notifier_init(x) do { register_latency_notifier(x); } while (0) + +#else /* CONFIG_SMP */ + +#define latency_notifier_init(x) do { } while (0) + +#endif /* CONFIG_SMP */ + +/** + * cpuidle_init - core initializer + */ +static int __init cpuidle_init(void) +{ + int ret; + + pm_idle_old = pm_idle; + + ret = cpuidle_add_class_sysfs(&cpu_sysdev_class); + if (ret) + return ret; + + register_hotcpu_notifier(&cpuidle_cpu_notifier); + + ret = sysdev_driver_register(&cpu_sysdev_class, &cpuidle_sysdev_driver); + + if (ret) { + cpuidle_remove_class_sysfs(&cpu_sysdev_class); + printk(KERN_ERR "cpuidle: failed to initialize\n"); + return ret; + } + + latency_notifier_init(&cpuidle_latency_notifier); + + return 0; +} + +core_initcall(cpuidle_init); diff --git a/drivers/cpuidle/cpuidle.h b/drivers/cpuidle/cpuidle.h new file mode 100644 index 0000000..8bbc090 --- /dev/null +++ b/drivers/cpuidle/cpuidle.h @@ -0,0 +1,50 @@ +/* + * cpuidle.h - The internal header file + */ + +#ifndef __DRIVER_CPUIDLE_H +#define __DRIVER_CPUIDLE_H + +#include + +/* For internal use only */ +extern struct cpuidle_governor *cpuidle_curr_governor; +extern struct cpuidle_driver *cpuidle_curr_driver; +extern struct list_head cpuidle_drivers; +extern struct list_head cpuidle_governors; +extern struct list_head cpuidle_detected_devices; +extern struct mutex cpuidle_lock; + +/* idle loop */ +extern void cpuidle_install_idle_handler(void); +extern void cpuidle_uninstall_idle_handler(void); +extern void cpuidle_rescan_device(struct cpuidle_device *dev); + +/* drivers */ +extern int cpuidle_attach_driver(struct cpuidle_device *dev); +extern void cpuidle_detach_driver(struct cpuidle_device *dev); +extern int cpuidle_switch_driver(struct cpuidle_driver *drv); + +/* governors */ +extern int cpuidle_attach_governor(struct cpuidle_device *dev); +extern void cpuidle_detach_governor(struct cpuidle_device *dev); +extern int cpuidle_switch_governor(struct cpuidle_governor *gov); + +/* sysfs */ +extern int cpuidle_add_class_sysfs(struct sysdev_class *cls); +extern void cpuidle_remove_class_sysfs(struct sysdev_class *cls); +extern int cpuidle_add_driver_sysfs(struct cpuidle_device *device); +extern void cpuidle_remove_driver_sysfs(struct cpuidle_device *device); +extern int cpuidle_add_sysfs(struct sys_device *sysdev); +extern void cpuidle_remove_sysfs(struct sys_device *sysdev); + +/** + * cpuidle_device_can_idle - determines if a CPU can utilize the idle loop + * @dev: the target CPU + */ +static inline int cpuidle_device_can_idle(struct cpuidle_device *dev) +{ + return (dev->status == CPUIDLE_STATUS_DOIDLE); +} + +#endif /* __DRIVER_CPUIDLE_H */ diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c new file mode 100644 index 0000000..de8c627 --- /dev/null +++ b/drivers/cpuidle/driver.c @@ -0,0 +1,219 @@ +/* + * driver.c - driver support + * + * (C) 2006-2007 Venkatesh Pallipadi + * Shaohua Li + * Adam Belay + * + * This code is licenced under the GPL. + */ + +#include +#include +#include + +#include "cpuidle.h" + +LIST_HEAD(cpuidle_drivers); +struct cpuidle_driver *cpuidle_curr_driver; + + +/** + * cpuidle_attach_driver - attaches a driver to a CPU + * @dev: the target CPU + * + * Must be called with cpuidle_lock aquired. + */ +int cpuidle_attach_driver(struct cpuidle_device *dev) +{ + int ret; + + if (dev->status & CPUIDLE_STATUS_DRIVER_ATTACHED) + return -EIO; + + if (!try_module_get(cpuidle_curr_driver->owner)) + return -EINVAL; + + ret = cpuidle_curr_driver->init(dev); + if (ret) { + module_put(cpuidle_curr_driver->owner); + printk(KERN_ERR "cpuidle: driver %s failed to attach to cpu %d\n", + cpuidle_curr_driver->name, dev->cpu); + } else { + if (dev->status & CPUIDLE_STATUS_GOVERNOR_ATTACHED) + cpuidle_rescan_device(dev); + smp_wmb(); + dev->status |= CPUIDLE_STATUS_DRIVER_ATTACHED; + cpuidle_add_driver_sysfs(dev); + } + + return ret; +} + +/** + * cpuidle_detach_govenor - detaches a driver from a CPU + * @dev: the target CPU + * + * Must be called with cpuidle_lock aquired. + */ +void cpuidle_detach_driver(struct cpuidle_device *dev) +{ + if (dev->status & CPUIDLE_STATUS_DRIVER_ATTACHED) { + cpuidle_remove_driver_sysfs(dev); + dev->status &= ~CPUIDLE_STATUS_DRIVER_ATTACHED; + if (cpuidle_curr_driver->exit) + cpuidle_curr_driver->exit(dev); + module_put(cpuidle_curr_driver->owner); + } +} + +/** + * __cpuidle_find_driver - finds a driver of the specified name + * @str: the name + * + * Must be called with cpuidle_lock aquired. + */ +static struct cpuidle_driver * __cpuidle_find_driver(const char *str) +{ + struct cpuidle_driver *drv; + + list_for_each_entry(drv, &cpuidle_drivers, driver_list) + if (!strnicmp(str, drv->name, CPUIDLE_NAME_LEN)) + return drv; + + return NULL; +} + +/** + * cpuidle_switch_driver - changes the driver + * @drv: the new target driver + * + * NOTE: "drv" can be NULL to specify disabled + * Must be called with cpuidle_lock aquired. + */ +int cpuidle_switch_driver(struct cpuidle_driver *drv) +{ + struct cpuidle_device *dev; + + if (drv == cpuidle_curr_driver) + return -EINVAL; + + cpuidle_uninstall_idle_handler(); + + if (cpuidle_curr_driver) + list_for_each_entry(dev, &cpuidle_detected_devices, device_list) + cpuidle_detach_driver(dev); + + cpuidle_curr_driver = drv; + + if (drv) { + list_for_each_entry(dev, &cpuidle_detected_devices, device_list) + cpuidle_attach_driver(dev); + if (cpuidle_curr_governor) + cpuidle_install_idle_handler(); + printk(KERN_INFO "cpuidle: using driver %s\n", drv->name); + } + + return 0; +} + +/** + * cpuidle_register_driver - registers a driver + * @drv: the driver + */ +int cpuidle_register_driver(struct cpuidle_driver *drv) +{ + int ret = -EEXIST; + + if (!drv || !drv->init) + return -EINVAL; + + mutex_lock(&cpuidle_lock); + if (__cpuidle_find_driver(drv->name) == NULL) { + ret = 0; + list_add_tail(&drv->driver_list, &cpuidle_drivers); + if (!cpuidle_curr_driver) + cpuidle_switch_driver(drv); + } + mutex_unlock(&cpuidle_lock); + + return ret; +} + +EXPORT_SYMBOL_GPL(cpuidle_register_driver); + +/** + * cpuidle_unregister_driver - unregisters a driver + * @drv: the driver + */ +void cpuidle_unregister_driver(struct cpuidle_driver *drv) +{ + if (!drv) + return; + + mutex_lock(&cpuidle_lock); + if (drv == cpuidle_curr_driver) + cpuidle_switch_driver(NULL); + list_del(&drv->driver_list); + mutex_unlock(&cpuidle_lock); +} + +EXPORT_SYMBOL_GPL(cpuidle_unregister_driver); + +/** + * cpuidle_force_redetect - redetects the idle states of a CPU + * + * @dev: the CPU to redetect + * + * Generally, the driver will call this when the supported states set has + * changed. (e.g. as the result of an ACPI transition to battery power) + */ +int cpuidle_force_redetect(struct cpuidle_device *dev) +{ + int uninstalled = 0; + + mutex_lock(&cpuidle_lock); + + if (!(dev->status & CPUIDLE_STATUS_DRIVER_ATTACHED) || + !cpuidle_curr_driver->redetect) { + mutex_unlock(&cpuidle_lock); + return -EIO; + } + + if (cpuidle_device_can_idle(dev)) { + uninstalled = 1; + cpuidle_uninstall_idle_handler(); + } + + cpuidle_remove_driver_sysfs(dev); + cpuidle_curr_driver->redetect(dev); + cpuidle_add_driver_sysfs(dev); + + if (cpuidle_device_can_idle(dev)) { + cpuidle_rescan_device(dev); + cpuidle_install_idle_handler(); + } + + /* other devices are still ok */ + if (uninstalled) + cpuidle_install_idle_handler(); + + mutex_unlock(&cpuidle_lock); + + return 0; +} + +EXPORT_SYMBOL_GPL(cpuidle_force_redetect); + +/** + * cpuidle_get_bm_activity - determines if BM activity has occured + */ +int cpuidle_get_bm_activity(void) +{ + if (cpuidle_curr_driver->bm_check) + return cpuidle_curr_driver->bm_check(); + else + return 0; +} +EXPORT_SYMBOL_GPL(cpuidle_get_bm_activity); + diff --git a/drivers/cpuidle/governor.c b/drivers/cpuidle/governor.c new file mode 100644 index 0000000..fa637fa --- /dev/null +++ b/drivers/cpuidle/governor.c @@ -0,0 +1,160 @@ +/* + * governor.c - governor support + * + * (C) 2006-2007 Venkatesh Pallipadi + * Shaohua Li + * Adam Belay + * + * This code is licenced under the GPL. + */ + +#include +#include +#include + +#include "cpuidle.h" + +LIST_HEAD(cpuidle_governors); +struct cpuidle_governor *cpuidle_curr_governor; + + +/** + * cpuidle_attach_governor - attaches a governor to a CPU + * @dev: the target CPU + * + * Must be called with cpuidle_lock aquired. + */ +int cpuidle_attach_governor(struct cpuidle_device *dev) +{ + int ret = 0; + + if(dev->status & CPUIDLE_STATUS_GOVERNOR_ATTACHED) + return -EIO; + + if (!try_module_get(cpuidle_curr_governor->owner)) + return -EINVAL; + + if (cpuidle_curr_governor->init) + ret = cpuidle_curr_governor->init(dev); + if (ret) { + module_put(cpuidle_curr_governor->owner); + printk(KERN_ERR "cpuidle: governor %s failed to attach to cpu %d\n", + cpuidle_curr_governor->name, dev->cpu); + } else { + if (dev->status & CPUIDLE_STATUS_DRIVER_ATTACHED) + cpuidle_rescan_device(dev); + smp_wmb(); + dev->status |= CPUIDLE_STATUS_GOVERNOR_ATTACHED; + } + + return ret; +} + +/** + * cpuidle_detach_govenor - detaches a governor from a CPU + * @dev: the target CPU + * + * Must be called with cpuidle_lock aquired. + */ +void cpuidle_detach_governor(struct cpuidle_device *dev) +{ + if (dev->status & CPUIDLE_STATUS_GOVERNOR_ATTACHED) { + dev->status &= ~CPUIDLE_STATUS_GOVERNOR_ATTACHED; + if (cpuidle_curr_governor->exit) + cpuidle_curr_governor->exit(dev); + module_put(cpuidle_curr_governor->owner); + } +} + +/** + * __cpuidle_find_governor - finds a governor of the specified name + * @str: the name + * + * Must be called with cpuidle_lock aquired. + */ +static struct cpuidle_governor * __cpuidle_find_governor(const char *str) +{ + struct cpuidle_governor *gov; + + list_for_each_entry(gov, &cpuidle_governors, governor_list) + if (!strnicmp(str, gov->name, CPUIDLE_NAME_LEN)) + return gov; + + return NULL; +} + +/** + * cpuidle_switch_governor - changes the governor + * @gov: the new target governor + * + * NOTE: "gov" can be NULL to specify disabled + * Must be called with cpuidle_lock aquired. + */ +int cpuidle_switch_governor(struct cpuidle_governor *gov) +{ + struct cpuidle_device *dev; + + if (gov == cpuidle_curr_governor) + return -EINVAL; + + cpuidle_uninstall_idle_handler(); + + if (cpuidle_curr_governor) + list_for_each_entry(dev, &cpuidle_detected_devices, device_list) + cpuidle_detach_governor(dev); + + cpuidle_curr_governor = gov; + + if (gov) { + list_for_each_entry(dev, &cpuidle_detected_devices, device_list) + cpuidle_attach_governor(dev); + if (cpuidle_curr_driver) + cpuidle_install_idle_handler(); + printk(KERN_INFO "cpuidle: using governor %s\n", gov->name); + } + + return 0; +} + +/** + * cpuidle_register_governor - registers a governor + * @gov: the governor + */ +int cpuidle_register_governor(struct cpuidle_governor *gov) +{ + int ret = -EEXIST; + + if (!gov || !gov->select_state) + return -EINVAL; + + mutex_lock(&cpuidle_lock); + if (__cpuidle_find_governor(gov->name) == NULL) { + ret = 0; + list_add_tail(&gov->governor_list, &cpuidle_governors); + if (!cpuidle_curr_governor) + cpuidle_switch_governor(gov); + } + mutex_unlock(&cpuidle_lock); + + return ret; +} + +EXPORT_SYMBOL_GPL(cpuidle_register_governor); + +/** + * cpuidle_unregister_governor - unregisters a governor + * @gov: the governor + */ +void cpuidle_unregister_governor(struct cpuidle_governor *gov) +{ + if (!gov) + return; + + mutex_lock(&cpuidle_lock); + if (gov == cpuidle_curr_governor) + cpuidle_switch_governor(NULL); + list_del(&gov->governor_list); + mutex_unlock(&cpuidle_lock); +} + +EXPORT_SYMBOL_GPL(cpuidle_unregister_governor); diff --git a/drivers/cpuidle/governors/Makefile b/drivers/cpuidle/governors/Makefile new file mode 100644 index 0000000..becf470 --- /dev/null +++ b/drivers/cpuidle/governors/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for cpuidle governors. +# + +obj-$(CONFIG_CPU_IDLE_GOV_LADDER) += ladder.o diff --git a/drivers/cpuidle/governors/ladder.c b/drivers/cpuidle/governors/ladder.c new file mode 100644 index 0000000..cf5a7a7 --- /dev/null +++ b/drivers/cpuidle/governors/ladder.c @@ -0,0 +1,227 @@ +/* + * ladder.c - the residency ladder algorithm + * + * Copyright (C) 2001, 2002 Andy Grover + * Copyright (C) 2001, 2002 Paul Diefenbaugh + * Copyright (C) 2004, 2005 Dominik Brodowski + * + * (C) 2006-2007 Venkatesh Pallipadi + * Shaohua Li + * Adam Belay + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define PROMOTION_COUNT 4 +#define DEMOTION_COUNT 1 + +/* + * bm_history -- bit-mask with a bit per jiffy of bus-master activity + * 1000 HZ: 0xFFFFFFFF: 32 jiffies = 32ms + * 800 HZ: 0xFFFFFFFF: 32 jiffies = 40ms + * 100 HZ: 0x0000000F: 4 jiffies = 40ms + * reduce history for more aggressive entry into C3 + */ +static unsigned int bm_history __read_mostly = + (HZ >= 800 ? 0xFFFFFFFF : ((1U << (HZ / 25)) - 1)); +module_param(bm_history, uint, 0644); + +struct ladder_device_state { + struct { + u32 promotion_count; + u32 demotion_count; + u32 promotion_time; + u32 demotion_time; + u32 bm; + } threshold; + struct { + int promotion_count; + int demotion_count; + } stats; +}; + +struct ladder_device { + struct ladder_device_state states[CPUIDLE_STATE_MAX]; + int bm_check:1; + unsigned long bm_check_timestamp; + unsigned long bm_activity; /* FIXME: bm activity should be global */ + int last_state_idx; +}; + +/** + * ladder_do_selection - prepares private data for a state change + * @ldev: the ladder device + * @old_idx: the current state index + * @new_idx: the new target state index + */ +static inline void ladder_do_selection(struct ladder_device *ldev, + int old_idx, int new_idx) +{ + ldev->states[old_idx].stats.promotion_count = 0; + ldev->states[old_idx].stats.demotion_count = 0; + ldev->last_state_idx = new_idx; +} + +/** + * ladder_select_state - selects the next state to enter + * @dev: the CPU + */ +static int ladder_select_state(struct cpuidle_device *dev) +{ + struct ladder_device *ldev = dev->governor_data; + struct ladder_device_state *last_state; + int last_residency, last_idx = ldev->last_state_idx; + + if (unlikely(!ldev)) + return 0; + + last_state = &ldev->states[last_idx]; + + /* demote if within BM threshold */ + if (ldev->bm_check) { + unsigned long diff; + + diff = jiffies - ldev->bm_check_timestamp; + if (diff > 31) + diff = 31; + + ldev->bm_activity <<= diff; + if (cpuidle_get_bm_activity()) + ldev->bm_activity |= ((1 << diff) - 1); + + ldev->bm_check_timestamp = jiffies; + if ((last_idx > 0) && + (last_state->threshold.bm & ldev->bm_activity)) { + ladder_do_selection(ldev, last_idx, last_idx - 1); + return last_idx - 1; + } + } + + if (dev->states[last_idx].flags & CPUIDLE_FLAG_TIME_VALID) + last_residency = cpuidle_get_last_residency(dev) - dev->states[last_idx].exit_latency; + else + last_residency = last_state->threshold.promotion_time + 1; + + /* consider promotion */ + if (last_idx < dev->state_count - 1 && + last_residency > last_state->threshold.promotion_time && + dev->states[last_idx + 1].exit_latency <= system_latency_constraint()) { + last_state->stats.promotion_count++; + last_state->stats.demotion_count = 0; + if (last_state->stats.promotion_count >= last_state->threshold.promotion_count) { + ladder_do_selection(ldev, last_idx, last_idx + 1); + return last_idx + 1; + } + } + + /* consider demotion */ + if (last_idx > 0 && + last_residency < last_state->threshold.demotion_time) { + last_state->stats.demotion_count++; + last_state->stats.promotion_count = 0; + if (last_state->stats.demotion_count >= last_state->threshold.demotion_count) { + ladder_do_selection(ldev, last_idx, last_idx - 1); + return last_idx - 1; + } + } + + /* otherwise remain at the current state */ + return last_idx; +} + +/** + * ladder_scan_device - scans a CPU's states and does setup + * @dev: the CPU + */ +static void ladder_scan_device(struct cpuidle_device *dev) +{ + int i, bm_check = 0; + struct ladder_device *ldev = dev->governor_data; + struct ladder_device_state *lstate; + struct cpuidle_state *state; + + ldev->last_state_idx = 0; + ldev->bm_check_timestamp = 0; + ldev->bm_activity = 0; + + for (i = 0; i < dev->state_count; i++) { + state = &dev->states[i]; + lstate = &ldev->states[i]; + + lstate->stats.promotion_count = 0; + lstate->stats.demotion_count = 0; + + lstate->threshold.promotion_count = PROMOTION_COUNT; + lstate->threshold.demotion_count = DEMOTION_COUNT; + + if (i < dev->state_count - 1) + lstate->threshold.promotion_time = state->exit_latency; + if (i > 0) + lstate->threshold.demotion_time = state->exit_latency; + if (state->flags & CPUIDLE_FLAG_CHECK_BM) { + lstate->threshold.bm = bm_history; + bm_check = 1; + } else + lstate->threshold.bm = 0; + } + + ldev->bm_check = bm_check; +} + +/** + * ladder_init_device - initializes a CPU-instance + * @dev: the CPU + */ +static int ladder_init_device(struct cpuidle_device *dev) +{ + dev->governor_data = kmalloc(sizeof(struct ladder_device), GFP_KERNEL); + + return !dev->governor_data; +} + +/** + * ladder_exit_device - exits a CPU-instance + * @dev: the CPU + */ +static void ladder_exit_device(struct cpuidle_device *dev) +{ + kfree(dev->governor_data); +} + +static struct cpuidle_governor ladder_governor = { + .name = "ladder", + .init = ladder_init_device, + .exit = ladder_exit_device, + .scan = ladder_scan_device, + .select_state = ladder_select_state, + .owner = THIS_MODULE, +}; + +/** + * init_ladder - initializes the governor + */ +static int __init init_ladder(void) +{ + return cpuidle_register_governor(&ladder_governor); +} + +/** + * exit_ladder - exits the governor + */ +static void __exit exit_ladder(void) +{ + cpuidle_unregister_governor(&ladder_governor); +} + +MODULE_LICENSE("GPL"); +module_init(init_ladder); +module_exit(exit_ladder); diff --git a/drivers/cpuidle/sysfs.c b/drivers/cpuidle/sysfs.c new file mode 100644 index 0000000..8822d0d --- /dev/null +++ b/drivers/cpuidle/sysfs.c @@ -0,0 +1,340 @@ +/* + * sysfs.c - sysfs support + * + * (C) 2006-2007 Shaohua Li + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include + +#include "cpuidle.h" + +static ssize_t show_available_drivers(struct sys_device *dev, char *buf) +{ + ssize_t i = 0; + struct cpuidle_driver *tmp; + + mutex_lock(&cpuidle_lock); + list_for_each_entry(tmp, &cpuidle_drivers, driver_list) { + if (i >= (ssize_t)((PAGE_SIZE/sizeof(char)) - CPUIDLE_NAME_LEN - 2)) + goto out; + i += scnprintf(&buf[i], CPUIDLE_NAME_LEN, "%s ", tmp->name); + } +out: + i+= sprintf(&buf[i], "\n"); + mutex_unlock(&cpuidle_lock); + return i; +} + +static ssize_t show_available_governors(struct sys_device *dev, char *buf) +{ + ssize_t i = 0; + struct cpuidle_governor *tmp; + + mutex_lock(&cpuidle_lock); + list_for_each_entry(tmp, &cpuidle_governors, governor_list) { + if (i >= (ssize_t)((PAGE_SIZE/sizeof(char)) - CPUIDLE_NAME_LEN - 2)) + goto out; + i += scnprintf(&buf[i], CPUIDLE_NAME_LEN, "%s ", tmp->name); + } + if (list_empty(&cpuidle_governors)) + i+= sprintf(&buf[i], "no governors"); +out: + i+= sprintf(&buf[i], "\n"); + mutex_unlock(&cpuidle_lock); + return i; +} + +static ssize_t show_current_driver(struct sys_device *dev, char *buf) +{ + ssize_t ret; + + mutex_lock(&cpuidle_lock); + ret = sprintf(buf, "%s\n", cpuidle_curr_driver->name); + mutex_unlock(&cpuidle_lock); + return ret; +} + +static ssize_t store_current_driver(struct sys_device *dev, + const char *buf, size_t count) +{ + char str[CPUIDLE_NAME_LEN]; + int len = count; + struct cpuidle_driver *tmp, *found = NULL; + + if (len > CPUIDLE_NAME_LEN) + len = CPUIDLE_NAME_LEN; + + if (sscanf(buf, "%s", str) != 1) + return -EINVAL; + + mutex_lock(&cpuidle_lock); + list_for_each_entry(tmp, &cpuidle_drivers, driver_list) { + if (strncmp(tmp->name, str, CPUIDLE_NAME_LEN) == 0) { + found = tmp; + break; + } + } + if (found) + cpuidle_switch_driver(found); + mutex_unlock(&cpuidle_lock); + + return count; +} + +static ssize_t show_current_governor(struct sys_device *dev, char *buf) +{ + ssize_t i; + + mutex_lock(&cpuidle_lock); + if (cpuidle_curr_governor) + i = sprintf(buf, "%s\n", cpuidle_curr_governor->name); + else + i = sprintf(buf, "no governor\n"); + mutex_unlock(&cpuidle_lock); + + return i; +} + +static ssize_t store_current_governor(struct sys_device *dev, + const char *buf, size_t count) +{ + char str[CPUIDLE_NAME_LEN]; + int len = count; + struct cpuidle_governor *tmp, *found = NULL; + + if (len > CPUIDLE_NAME_LEN) + len = CPUIDLE_NAME_LEN; + + if (sscanf(buf, "%s", str) != 1) + return -EINVAL; + + mutex_lock(&cpuidle_lock); + list_for_each_entry(tmp, &cpuidle_governors, governor_list) { + if (strncmp(tmp->name, str, CPUIDLE_NAME_LEN) == 0) { + found = tmp; + break; + } + } + if (found) + cpuidle_switch_governor(found); + mutex_unlock(&cpuidle_lock); + + return count; +} + +static SYSDEV_ATTR(available_drivers, 0444, show_available_drivers, NULL); +static SYSDEV_ATTR(available_governors, 0444, show_available_governors, NULL); +static SYSDEV_ATTR(current_driver, 0644, show_current_driver, + store_current_driver); +static SYSDEV_ATTR(current_governor, 0644, show_current_governor, + store_current_governor); + +static struct attribute *cpuclass_default_attrs[] = { + &attr_available_drivers.attr, + &attr_available_governors.attr, + &attr_current_driver.attr, + &attr_current_governor.attr, + NULL +}; + +static struct attribute_group cpuclass_attr_group = { + .attrs = cpuclass_default_attrs, + .name = "cpuidle", +}; + +/** + * cpuidle_add_class_sysfs - add CPU global sysfs attributes + */ +int cpuidle_add_class_sysfs(struct sysdev_class *cls) +{ + return sysfs_create_group(&cls->kset.kobj, &cpuclass_attr_group); +} + +/** + * cpuidle_remove_class_sysfs - remove CPU global sysfs attributes + */ +void cpuidle_remove_class_sysfs(struct sysdev_class *cls) +{ + sysfs_remove_group(&cls->kset.kobj, &cpuclass_attr_group); +} + +struct cpuidle_attr { + struct attribute attr; + ssize_t (*show)(struct cpuidle_device *, char *); + ssize_t (*store)(struct cpuidle_device *, const char *, size_t count); +}; + +#define define_one_ro(_name, show) \ + static struct cpuidle_attr attr_##_name = __ATTR(_name, 0444, show, NULL) +#define define_one_rw(_name, show, store) \ + static struct cpuidle_attr attr_##_name = __ATTR(_name, 0644, show, store) + +#define kobj_to_cpuidledev(k) container_of(k, struct cpuidle_device, kobj) +#define attr_to_cpuidleattr(a) container_of(a, struct cpuidle_attr, attr) +static ssize_t cpuidle_show(struct kobject * kobj, struct attribute * attr ,char * buf) +{ + int ret = -EIO; + struct cpuidle_device *dev = kobj_to_cpuidledev(kobj); + struct cpuidle_attr * cattr = attr_to_cpuidleattr(attr); + + if (cattr->show) { + mutex_lock(&cpuidle_lock); + ret = cattr->show(dev, buf); + mutex_unlock(&cpuidle_lock); + } + return ret; +} + +static ssize_t cpuidle_store(struct kobject * kobj, struct attribute * attr, + const char * buf, size_t count) +{ + int ret = -EIO; + struct cpuidle_device *dev = kobj_to_cpuidledev(kobj); + struct cpuidle_attr * cattr = attr_to_cpuidleattr(attr); + + if (cattr->store) { + mutex_lock(&cpuidle_lock); + ret = cattr->store(dev, buf, count); + mutex_unlock(&cpuidle_lock); + } + return ret; +} + +static struct sysfs_ops cpuidle_sysfs_ops = { + .show = cpuidle_show, + .store = cpuidle_store, +}; + +static struct kobj_type ktype_cpuidle = { + .sysfs_ops = &cpuidle_sysfs_ops, +}; + +struct cpuidle_state_attr { + struct attribute attr; + ssize_t (*show)(struct cpuidle_state *, char *); + ssize_t (*store)(struct cpuidle_state *, const char *, size_t); +}; + +#define define_one_state_ro(_name, show) \ +static struct cpuidle_state_attr attr_##_name = __ATTR(_name, 0444, show, NULL) + +#define define_show_state_function(_name) \ +static ssize_t show_state_##_name(struct cpuidle_state *state, char *buf) \ +{ \ + return sprintf(buf, "%d\n", state->_name);\ +} + +define_show_state_function(exit_latency) +define_show_state_function(power_usage) +define_show_state_function(usage) +define_show_state_function(time) +define_one_state_ro(latency, show_state_exit_latency); +define_one_state_ro(power, show_state_power_usage); +define_one_state_ro(usage, show_state_usage); +define_one_state_ro(time, show_state_time); + +static struct attribute *cpuidle_state_default_attrs[] = { + &attr_latency.attr, + &attr_power.attr, + &attr_usage.attr, + &attr_time.attr, + NULL +}; + +#define kobj_to_state(k) container_of(k, struct cpuidle_state, kobj) +#define attr_to_stateattr(a) container_of(a, struct cpuidle_state_attr, attr) +static ssize_t cpuidle_state_show(struct kobject * kobj, + struct attribute * attr ,char * buf) +{ + int ret = -EIO; + struct cpuidle_state *state = kobj_to_state(kobj); + struct cpuidle_state_attr * cattr = attr_to_stateattr(attr); + + if (cattr->show) + ret = cattr->show(state, buf); + + return ret; +} + +static struct sysfs_ops cpuidle_state_sysfs_ops = { + .show = cpuidle_state_show, +}; + +static struct kobj_type ktype_state_cpuidle = { + .sysfs_ops = &cpuidle_state_sysfs_ops, + .default_attrs = cpuidle_state_default_attrs, +}; + +/** + * cpuidle_add_driver_sysfs - adds driver-specific sysfs attributes + * @device: the target device + */ +int cpuidle_add_driver_sysfs(struct cpuidle_device *device) +{ + int i, ret; + struct cpuidle_state *state; + + /* state statistics */ + for (i = 0; i < device->state_count; i++) { + state = &device->states[i]; + state->kobj.parent = &device->kobj; + state->kobj.ktype = &ktype_state_cpuidle; + kobject_set_name(&state->kobj, "state%d", i); + ret = kobject_register(&state->kobj); + if (ret) + goto error_state; + } + + return 0; + +error_state: + for (i = i - 1; i >= 0; i--) + kobject_unregister(&device->states[i].kobj); + return ret; +} + +/** + * cpuidle_remove_driver_sysfs - removes driver-specific sysfs attributes + * @device: the target device + */ +void cpuidle_remove_driver_sysfs(struct cpuidle_device *device) +{ + int i; + + for (i = 0; i < device->state_count; i++) + kobject_unregister(&device->states[i].kobj); +} + +/** + * cpuidle_add_sysfs - creates a sysfs instance for the target device + * @sysdev: the target device + */ +int cpuidle_add_sysfs(struct sys_device *sysdev) +{ + int cpu = sysdev->id; + struct cpuidle_device *dev; + + dev = &per_cpu(cpuidle_devices, cpu); + dev->kobj.parent = &sysdev->kobj; + dev->kobj.ktype = &ktype_cpuidle; + kobject_set_name(&dev->kobj, "%s", "cpuidle"); + return kobject_register(&dev->kobj); +} + +/** + * cpuidle_remove_sysfs - deletes a sysfs instance on the target device + * @sysdev: the target device + */ +void cpuidle_remove_sysfs(struct sys_device *sysdev) +{ + int cpu = sysdev->id; + struct cpuidle_device *dev; + + dev = &per_cpu(cpuidle_devices, cpu); + kobject_unregister(&dev->kobj); +} diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 80b199f..5d2bcbf 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -122,4 +122,41 @@ config SONY_LAPTOP Read for more information. +config ACPI_IBM + tristate "IBM ThinkPad Laptop Extras" + depends on X86 && ACPI + select BACKLIGHT_CLASS_DEVICE + ---help--- + This is a Linux ACPI driver for the IBM ThinkPad laptops. It adds + support for Fn-Fx key combinations, Bluetooth control, video + output switching, ThinkLight control, UltraBay eject and more. + For more information about this driver see + and . + + If you have an IBM ThinkPad laptop, say Y or M here. + +config ACPI_IBM_DOCK + bool "Legacy Docking Station Support" + depends on ACPI_IBM + depends on ACPI_DOCK=n + default n + ---help--- + Allows the ibm_acpi driver to handle docking station events. + This support is obsoleted by CONFIG_HOTPLUG_PCI_ACPI. It will + allow locking and removing the laptop from the docking station, + but will not properly connect PCI devices. + + If you are not sure, say N here. + +config ACPI_IBM_BAY + bool "Legacy Removable Bay Support" + depends on ACPI_IBM + default y + ---help--- + Allows the ibm_acpi driver to handle removable bays. It will allow + disabling the device in the bay, and also generate notifications when + the bay lever is ejected or inserted. + + If you are not sure, say Y here. + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7793ccd..848b398 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_TIFM_CORE) += tifm_c obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o +obj-$(CONFIG_ACPI_IBM) += ibm_acpi.o diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 4b23212..65c32a9 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -3,7 +3,7 @@ * * * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor - * Copyright (C) 2006 Corentin Chary + * Copyright (C) 2006-2007 Corentin Chary * * 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 @@ -48,7 +48,7 @@ #include #include #include -#define ASUS_LAPTOP_VERSION "0.40" +#define ASUS_LAPTOP_VERSION "0.41" #define ASUS_HOTK_NAME "Asus Laptop Support" #define ASUS_HOTK_CLASS "hotkey" @@ -81,7 +81,8 @@ #define MLED_ON 0x04 //mail LED #define TLED_ON 0x08 //touchpad LED #define RLED_ON 0x10 //Record LED #define PLED_ON 0x20 //Phone LED -#define LCD_ON 0x40 //LCD backlight +#define GLED_ON 0x40 //Gaming LED +#define LCD_ON 0x80 //LCD backlight #define ASUS_LOG ASUS_HOTK_FILE ": " #define ASUS_ERR KERN_ERR ASUS_LOG @@ -94,6 +95,19 @@ MODULE_AUTHOR("Julien Lerouge, Karol Koz MODULE_DESCRIPTION(ASUS_HOTK_NAME); MODULE_LICENSE("GPL"); +/* 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 will do nothing + * 0x1 will allow to control the device with Fn+Fx key. + * 0x4 will send an ACPI event (0x88) while pressing the Fn+Fx key + * 0x5 like 0x1 or 0x4 + * So, if something doesn't work as you want, just try other values =) + */ +static uint wapf = 1; +module_param(wapf, uint, 0644); +MODULE_PARM_DESC(wapf, "WAPF value"); + #define ASUS_HANDLE(object, paths...) \ static acpi_handle object##_handle = NULL; \ static char *object##_paths[] = { paths } @@ -103,6 +117,7 @@ ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX " ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED"); ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */ ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */ +ASUS_HANDLE(gled_set, ASUS_HOTK_PREFIX "GLED"); /* G1, G2 (probably) */ /* LEDD */ ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM"); @@ -221,6 +236,7 @@ ASUS_LED(mled, "mail"); ASUS_LED(tled, "touchpad"); ASUS_LED(rled, "record"); ASUS_LED(pled, "phone"); +ASUS_LED(gled, "gaming"); /* * This function evaluates an ACPI method, given an int as parameter, the @@ -245,32 +261,19 @@ static int write_acpi_int(acpi_handle ha return (status == AE_OK); } -static int read_acpi_int(acpi_handle handle, const char *method, int *val, - struct acpi_object_list *params) -{ - struct acpi_buffer output; - union acpi_object out_obj; - acpi_status status; - - output.length = sizeof(out_obj); - output.pointer = &out_obj; - - status = acpi_evaluate_object(handle, (char *)method, params, &output); - *val = out_obj.integer.value; - return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER); -} - static int read_wireless_status(int mask) { - int status; + ulong status; + acpi_status rv = AE_OK; if (!wireless_status_handle) return (hotk->status & mask) ? 1 : 0; - if (read_acpi_int(wireless_status_handle, NULL, &status, NULL)) { - return (status & mask) ? 1 : 0; - } else + rv = acpi_evaluate_integer(wireless_status_handle, NULL, NULL, &status); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading Wireless status\n"); + else + return (status & mask) ? 1 : 0; return (hotk->status & mask) ? 1 : 0; } @@ -285,19 +288,28 @@ static int read_status(int mask) return (hotk->status & mask) ? 1 : 0; } -static void write_status(acpi_handle handle, int out, int mask, int invert) +static void write_status(acpi_handle handle, int out, int mask) { hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask); - if (invert) /* invert target value */ + switch (mask) { + case MLED_ON: out = !out & 0x1; + break; + case GLED_ON: + out = (out & 0x1) + 1; + break; + default: + out &= 0x1; + break; + } if (handle && !write_acpi_int(handle, NULL, out, NULL)) - printk(ASUS_WARNING " write failed\n"); + printk(ASUS_WARNING " write failed %x\n", mask); } /* /sys/class/led handlers */ -#define ASUS_LED_HANDLER(object, mask, invert) \ +#define ASUS_LED_HANDLER(object, mask) \ static void object##_led_set(struct led_classdev *led_cdev, \ enum led_brightness value) \ { \ @@ -307,13 +319,14 @@ #define ASUS_LED_HANDLER(object, mask, i static void object##_led_update(struct work_struct *ignored) \ { \ int value = object##_led_wk; \ - write_status(object##_set_handle, value, (mask), (invert)); \ + write_status(object##_set_handle, value, (mask)); \ } -ASUS_LED_HANDLER(mled, MLED_ON, 1); -ASUS_LED_HANDLER(pled, PLED_ON, 0); -ASUS_LED_HANDLER(rled, RLED_ON, 0); -ASUS_LED_HANDLER(tled, TLED_ON, 0); +ASUS_LED_HANDLER(mled, MLED_ON); +ASUS_LED_HANDLER(pled, PLED_ON); +ASUS_LED_HANDLER(rled, RLED_ON); +ASUS_LED_HANDLER(tled, TLED_ON); +ASUS_LED_HANDLER(gled, GLED_ON); static int get_lcd_state(void) { @@ -338,7 +351,7 @@ static int set_lcd_state(int value) printk(ASUS_WARNING "Error switching LCD\n"); } - write_status(NULL, lcd, LCD_ON, 0); + write_status(NULL, lcd, LCD_ON); return 0; } @@ -354,9 +367,11 @@ static void lcd_blank(int blank) static int read_brightness(struct backlight_device *bd) { - int value; + ulong value; + acpi_status rv = AE_OK; - if (!read_acpi_int(brightness_get_handle, NULL, &value, NULL)) + rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading brightness\n"); return value; @@ -403,8 +418,10 @@ static ssize_t show_infos(struct device struct device_attribute *attr, char *page) { int len = 0; - int temp; + ulong temp; char buf[16]; //enough for all info + acpi_status rv = AE_OK; + /* * We use the easy way, we don't care of off and count, so we don't set eof * to 1 @@ -418,9 +435,10 @@ static ssize_t show_infos(struct device * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. * The significance of others is yet to be found. */ - if (read_acpi_int(hotk->handle, "SFUN", &temp, NULL)) - len += - sprintf(page + len, "SFUN value : 0x%04x\n", temp); + rv = acpi_evaluate_integer(hotk->handle, "SFUN", NULL, &temp); + if (!ACPI_FAILURE(rv)) + len += sprintf(page + len, "SFUN value : 0x%04x\n", + (uint) temp); /* * Another value for userspace: the ASYM method returns 0x02 for * battery low and 0x04 for battery critical, its readings tend to be @@ -428,9 +446,10 @@ static ssize_t show_infos(struct device * Note: since not all the laptops provide this method, errors are * silently ignored. */ - if (read_acpi_int(hotk->handle, "ASYM", &temp, NULL)) - len += - sprintf(page + len, "ASYM value : 0x%04x\n", temp); + rv = acpi_evaluate_integer(hotk->handle, "ASYM", NULL, &temp); + if (!ACPI_FAILURE(rv)) + len += sprintf(page + len, "ASYM value : 0x%04x\n", + (uint) temp); if (asus_info) { snprintf(buf, 16, "%d", asus_info->length); len += sprintf(page + len, "DSDT length : %s\n", buf); @@ -465,7 +484,7 @@ static int parse_arg(const char *buf, un } static ssize_t store_status(const char *buf, size_t count, - acpi_handle handle, int mask, int invert) + acpi_handle handle, int mask) { int rv, value; int out = 0; @@ -474,7 +493,7 @@ static ssize_t store_status(const char * if (rv > 0) out = value ? 1 : 0; - write_status(handle, out, mask, invert); + write_status(handle, out, mask); return rv; } @@ -515,7 +534,7 @@ static ssize_t show_wlan(struct device * static ssize_t store_wlan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, wl_switch_handle, WL_ON, 0); + return store_status(buf, count, wl_switch_handle, WL_ON); } /* @@ -531,7 +550,7 @@ static ssize_t store_bluetooth(struct de struct device_attribute *attr, const char *buf, size_t count) { - return store_status(buf, count, bt_switch_handle, BT_ON, 0); + return store_status(buf, count, bt_switch_handle, BT_ON); } /* @@ -547,12 +566,15 @@ static void set_display(int value) static int read_display(void) { - int value = 0; + ulong value = 0; + acpi_status rv = AE_OK; /* In most of the case, we know how to set the display, but sometime we can't read it */ if (display_get_handle) { - if (!read_acpi_int(display_get_handle, NULL, &value, NULL)) + rv = acpi_evaluate_integer(display_get_handle, NULL, + NULL, &value); + if (ACPI_FAILURE(rv)) printk(ASUS_WARNING "Error reading display status\n"); } @@ -656,10 +678,10 @@ static void asus_hotk_notify(acpi_handle * switched */ if (event == ATKD_LCD_ON) { - write_status(NULL, 1, LCD_ON, 0); + write_status(NULL, 1, LCD_ON); lcd_blank(FB_BLANK_UNBLANK); } else if (event == ATKD_LCD_OFF) { - write_status(NULL, 0, LCD_ON, 0); + write_status(NULL, 0, LCD_ON); lcd_blank(FB_BLANK_POWERDOWN); } @@ -771,7 +793,7 @@ static int asus_hotk_get_info(void) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *model = NULL; - int bsts_result, hwrs_result; + ulong bsts_result, hwrs_result; char *string = NULL; acpi_status status; @@ -794,11 +816,16 @@ static int asus_hotk_get_info(void) } /* This needs to be called for some laptops to init properly */ - if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result, NULL)) + status = + acpi_evaluate_integer(hotk->handle, "BSTS", NULL, &bsts_result); + if (ACPI_FAILURE(status)) printk(ASUS_WARNING "Error calling BSTS\n"); else if (bsts_result) printk(ASUS_NOTICE "BSTS called, 0x%02x returned\n", - bsts_result); + (uint) bsts_result); + + /* This too ... */ + write_acpi_int(hotk->handle, "CWAP", wapf, NULL); /* * Try to match the object returned by INIT to the specific model. @@ -831,6 +858,7 @@ static int asus_hotk_get_info(void) ASUS_HANDLE_INIT(tled_set); ASUS_HANDLE_INIT(rled_set); ASUS_HANDLE_INIT(pled_set); + ASUS_HANDLE_INIT(gled_set); ASUS_HANDLE_INIT(ledd_set); @@ -840,7 +868,9 @@ static int asus_hotk_get_info(void) * The significance of others is yet to be found. * If we don't find the method, we assume the device are present. */ - if (!read_acpi_int(hotk->handle, "HRWS", &hwrs_result, NULL)) + status = + acpi_evaluate_integer(hotk->handle, "HRWS", NULL, &hwrs_result); + if (ACPI_FAILURE(status)) hwrs_result = WL_HWRS | BT_HWRS; if (hwrs_result & WL_HWRS) @@ -928,11 +958,15 @@ static int asus_hotk_add(struct acpi_dev asus_hotk_found = 1; /* WLED and BLED are on by default */ - write_status(bt_switch_handle, 1, BT_ON, 0); - write_status(wl_switch_handle, 1, WL_ON, 0); + write_status(bt_switch_handle, 1, BT_ON); + write_status(wl_switch_handle, 1, WL_ON); + + /* If the h/w switch is off, we need to check the real status */ + write_status(NULL, read_status(BT_ON), BT_ON); + write_status(NULL, read_status(WL_ON), WL_ON); /* LCD Backlight is on by default */ - write_status(NULL, 1, LCD_ON, 0); + write_status(NULL, 1, LCD_ON); /* LED display is off by default */ hotk->ledd_status = 0xFFF; @@ -991,6 +1025,7 @@ static void asus_led_exit(void) ASUS_LED_UNREGISTER(tled); ASUS_LED_UNREGISTER(pled); ASUS_LED_UNREGISTER(rled); + ASUS_LED_UNREGISTER(gled); destroy_workqueue(led_workqueue); } @@ -1062,6 +1097,10 @@ static int asus_led_init(struct device * if (rv) return rv; + rv = ASUS_LED_REGISTER(gled, dev); + if (rv) + return rv; + led_workqueue = create_singlethread_workqueue("led_workqueue"); if (!led_workqueue) return -ENOMEM; diff --git a/drivers/misc/ibm_acpi.c b/drivers/misc/ibm_acpi.c new file mode 100644 index 0000000..ae03b8f --- /dev/null +++ b/drivers/misc/ibm_acpi.c @@ -0,0 +1,2783 @@ +/* + * ibm_acpi.c - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#define IBM_VERSION "0.13" + +/* + * Changelog: + * + * 2006-11-22 0.13 new maintainer + * changelog now lives in git commit history, and will + * not be updated further in-file. + * + * 2005-08-17 0.12 fix compilation on 2.6.13-rc kernels + * 2005-03-17 0.11 support for 600e, 770x + * thanks to Jamie Lentin + * support for 770e, G41 + * G40 and G41 don't have a thinklight + * temperatures no longer experimental + * experimental brightness control + * experimental volume control + * experimental fan enable/disable + * 2005-01-16 0.10 fix module loading on R30, R31 + * 2005-01-16 0.9 support for 570, R30, R31 + * ultrabay support on A22p, A3x + * limit arg for cmos, led, beep, drop experimental status + * more capable led control on A21e, A22p, T20-22, X20 + * experimental temperatures and fan speed + * experimental embedded controller register dump + * mark more functions as __init, drop incorrect __exit + * use MODULE_VERSION + * thanks to Henrik Brix Andersen + * fix parameter passing on module loading + * thanks to Rusty Russell + * thanks to Jim Radford + * 2004-11-08 0.8 fix init error case, don't return from a macro + * thanks to Chris Wright + * 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20 + * fix led control on A21e + * 2004-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device + * 2004-10-18 0.5 thinklight support on A21e, G40, R32, T20, T21, X20 + * proc file format changed + * video_switch command + * experimental cmos control + * experimental led control + * experimental acpi sounds + * 2004-09-16 0.4 support for module parameters + * hotkey mask can be prefixed by 0x + * video output switching + * video expansion control + * ultrabay eject support + * removed lcd brightness/on/off control, didn't work + * 2004-08-17 0.3 support for R40 + * lcd off, brightness control + * thinklight on/off + * 2004-08-14 0.2 support for T series, X20 + * bluetooth enable/disable + * hotkey events disabled by default + * removed fan control, currently useless + * 2004-08-09 0.1 initial release, support for X series + */ + +#include "ibm_acpi.h" + +MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); +MODULE_DESCRIPTION(IBM_DESC); +MODULE_VERSION(IBM_VERSION); +MODULE_LICENSE("GPL"); + +#define __unused __attribute__ ((unused)) + +/**************************************************************************** + **************************************************************************** + * + * ACPI Helpers and device model + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ACPI basic handles + */ + +static acpi_handle root_handle = NULL; + +#define IBM_HANDLE(object, parent, paths...) \ + static acpi_handle object##_handle; \ + static acpi_handle *object##_parent = &parent##_handle; \ + static char *object##_path; \ + static char *object##_paths[] = { paths } + +IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ + "\\_SB.PCI.ISA.EC", /* 570 */ + "\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */ + "\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */ + "\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */ + "\\_SB.PCI0.ICH3.EC0", /* R31 */ + "\\_SB.PCI0.LPC.EC", /* all others */ + ); + +IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ +IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ + + +/************************************************************************* + * Misc ACPI handles + */ + +IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */ + "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */ + "\\CMS", /* R40, R40e */ + ); /* all others */ + +IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ + "^HKEY", /* R30, R31 */ + "HKEY", /* all others */ + ); /* 570 */ + + +/************************************************************************* + * ACPI helpers + */ + +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...) +{ + char *fmt0 = fmt; + struct acpi_object_list params; + union acpi_object in_objs[IBM_MAX_ACPI_ARGS]; + struct acpi_buffer result, *resultp; + union acpi_object out_obj; + acpi_status status; + va_list ap; + char res_type; + int success; + int quiet; + + if (!*fmt) { + printk(IBM_ERR "acpi_evalf() called with empty format\n"); + return 0; + } + + if (*fmt == 'q') { + quiet = 1; + fmt++; + } else + quiet = 0; + + res_type = *(fmt++); + + params.count = 0; + params.pointer = &in_objs[0]; + + va_start(ap, fmt); + while (*fmt) { + char c = *(fmt++); + switch (c) { + case 'd': /* int */ + in_objs[params.count].integer.value = va_arg(ap, int); + in_objs[params.count++].type = ACPI_TYPE_INTEGER; + break; + /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", c); + return 0; + } + } + va_end(ap); + + if (res_type != 'v') { + result.length = sizeof(out_obj); + result.pointer = &out_obj; + resultp = &result; + } else + resultp = NULL; + + status = acpi_evaluate_object(handle, method, ¶ms, resultp); + + switch (res_type) { + case 'd': /* int */ + if (res) + *(int *)res = out_obj.integer.value; + success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER; + break; + case 'v': /* void */ + success = status == AE_OK; + break; + /* add more types as needed */ + default: + printk(IBM_ERR "acpi_evalf() called " + "with invalid format character '%c'\n", res_type); + return 0; + } + + if (!success && !quiet) + printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n", + method, fmt0, status); + + return success; +} + +static void __unused acpi_print_int(acpi_handle handle, char *method) +{ + int i; + + if (acpi_evalf(handle, &i, method, "d")) + printk(IBM_INFO "%s = 0x%x\n", method, i); + else + printk(IBM_ERR "error calling %s\n", method); +} + +static int acpi_ec_read(int i, u8 * p) +{ + int v; + + if (ecrd_handle) { + if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i)) + return 0; + *p = v; + } else { + if (ec_read(i, p) < 0) + return 0; + } + + return 1; +} + +static int acpi_ec_write(int i, u8 v) +{ + if (ecwr_handle) { + if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v)) + return 0; + } else { + if (ec_write(i, v) < 0) + return 0; + } + + return 1; +} + +static int _sta(acpi_handle handle) +{ + int status; + + if (!handle || !acpi_evalf(handle, &status, "_STA", "d")) + status = 0; + + return status; +} + +/************************************************************************* + * ACPI device model + */ + +static void __init ibm_handle_init(char *name, + acpi_handle * handle, acpi_handle parent, + char **paths, int num_paths, char **path) +{ + int i; + acpi_status status; + + for (i = 0; i < num_paths; i++) { + status = acpi_get_handle(parent, paths[i], handle); + if (ACPI_SUCCESS(status)) { + *path = paths[i]; + return; + } + } + + *handle = NULL; +} + +static void dispatch_notify(acpi_handle handle, u32 event, void *data) +{ + struct ibm_struct *ibm = data; + + if (!ibm || !ibm->notify) + return; + + ibm->notify(ibm, event); +} + +static int __init setup_notify(struct ibm_struct *ibm) +{ + acpi_status status; + int ret; + + if (!*ibm->handle) + return 0; + + ret = acpi_bus_get_device(*ibm->handle, &ibm->device); + if (ret < 0) { + printk(IBM_ERR "%s device not present\n", ibm->name); + return -ENODEV; + } + + acpi_driver_data(ibm->device) = ibm; + sprintf(acpi_device_class(ibm->device), "%s/%s", IBM_NAME, ibm->name); + + status = acpi_install_notify_handler(*ibm->handle, ibm->type, + dispatch_notify, ibm); + if (ACPI_FAILURE(status)) { + if (status == AE_ALREADY_EXISTS) { + printk(IBM_NOTICE "another device driver is already handling %s events\n", + ibm->name); + } else { + printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n", + ibm->name, status); + } + return -ENODEV; + } + ibm->notify_installed = 1; + return 0; +} + +static int __init ibm_device_add(struct acpi_device *device) +{ + return 0; +} + +static int __init register_ibmacpi_subdriver(struct ibm_struct *ibm) +{ + int ret; + + ibm->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); + if (!ibm->driver) { + printk(IBM_ERR "kmalloc(ibm->driver) failed\n"); + return -1; + } + + sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name); + ibm->driver->ids = ibm->hid; + ibm->driver->ops.add = &ibm_device_add; + + ret = acpi_bus_register_driver(ibm->driver); + if (ret < 0) { + printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", + ibm->hid, ret); + kfree(ibm->driver); + } + + return ret; +} + + +/**************************************************************************** + **************************************************************************** + * + * Procfs Helpers + * + **************************************************************************** + ****************************************************************************/ + +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct ibm_struct *ibm = data; + int len; + + if (!ibm || !ibm->read) + return -EINVAL; + + len = ibm->read(page); + if (len < 0) + return len; + + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data) +{ + struct ibm_struct *ibm = data; + char *kernbuf; + int ret; + + if (!ibm || !ibm->write) + return -EINVAL; + + kernbuf = kmalloc(count + 2, GFP_KERNEL); + if (!kernbuf) + return -ENOMEM; + + if (copy_from_user(kernbuf, userbuf, count)) { + kfree(kernbuf); + return -EFAULT; + } + + kernbuf[count] = 0; + strcat(kernbuf, ","); + ret = ibm->write(kernbuf); + if (ret == 0) + ret = count; + + kfree(kernbuf); + + return ret; +} + +static char *next_cmd(char **cmds) +{ + char *start = *cmds; + char *end; + + while ((end = strchr(start, ',')) && end == start) + start = end + 1; + + if (!end) + return NULL; + + *end = 0; + *cmds = end + 1; + return start; +} + + +/**************************************************************************** + **************************************************************************** + * + * Subdrivers + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ibm-acpi init subdriver + */ + +static int ibm_acpi_driver_init(void) +{ + printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); + printk(IBM_INFO "%s\n", IBM_URL); + + if (ibm_thinkpad_ec_found) + printk(IBM_INFO "ThinkPad EC firmware %s\n", + ibm_thinkpad_ec_found); + + return 0; +} + +static int ibm_acpi_driver_read(char *p) +{ + int len = 0; + + len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC); + len += sprintf(p + len, "version:\t%s\n", IBM_VERSION); + + return len; +} + +/************************************************************************* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static int hotkey_init(void) +{ + /* hotkey not supported on 570 */ + hotkey_supported = hkey_handle != NULL; + + if (hotkey_supported) { + /* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + A30, R30, R31, T20-22, X20-21, X22-24 */ + hotkey_mask_supported = + acpi_evalf(hkey_handle, NULL, "DHKN", "qv"); + + if (!hotkey_get(&hotkey_orig_status, &hotkey_orig_mask)) + return -ENODEV; + } + + return 0; +} + +static void hotkey_exit(void) +{ + if (hotkey_supported) + hotkey_set(hotkey_orig_status, hotkey_orig_mask); +} + +static void hotkey_notify(struct ibm_struct *ibm, u32 event) +{ + int hkey; + + if (acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) + acpi_bus_generate_event(ibm->device, event, hkey); + else { + printk(IBM_ERR "unknown hotkey event %d\n", event); + acpi_bus_generate_event(ibm->device, event, 0); + } +} + +static int hotkey_get(int *status, int *mask) +{ + if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) + return 0; + + if (hotkey_mask_supported) + if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) + return 0; + + return 1; +} + +static int hotkey_set(int status, int mask) +{ + int i; + + if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) + return 0; + + if (hotkey_mask_supported) + for (i = 0; i < 32; i++) { + int bit = ((1 << i) & mask) != 0; + if (!acpi_evalf(hkey_handle, + NULL, "MHKM", "vdd", i + 1, bit)) + return 0; + } + + return 1; +} + +static int hotkey_read(char *p) +{ + int status, mask; + int len = 0; + + if (!hotkey_supported) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + + if (!hotkey_get(&status, &mask)) + return -EIO; + + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); + if (hotkey_mask_supported) { + len += sprintf(p + len, "mask:\t\t0x%04x\n", mask); + len += sprintf(p + len, + "commands:\tenable, disable, reset, \n"); + } else { + len += sprintf(p + len, "mask:\t\tnot supported\n"); + len += sprintf(p + len, "commands:\tenable, disable, reset\n"); + } + + return len; +} + +static int hotkey_write(char *buf) +{ + int status, mask; + char *cmd; + int do_cmd = 0; + + if (!hotkey_supported) + return -ENODEV; + + if (!hotkey_get(&status, &mask)) + return -EIO; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status = 1; + } else if (strlencmp(cmd, "disable") == 0) { + status = 0; + } else if (strlencmp(cmd, "reset") == 0) { + status = hotkey_orig_status; + mask = hotkey_orig_mask; + } else if (sscanf(cmd, "0x%x", &mask) == 1) { + /* mask set */ + } else if (sscanf(cmd, "%x", &mask) == 1) { + /* mask set */ + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !hotkey_set(status, mask)) + return -EIO; + + return 0; +} + +/************************************************************************* + * Bluetooth subdriver + */ + +static int bluetooth_supported; + +static int bluetooth_init(void) +{ + /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ + bluetooth_supported = hkey_handle && + acpi_evalf(hkey_handle, NULL, "GBDC", "qv"); + + return 0; +} + +static int bluetooth_status(void) +{ + int status; + + if (!bluetooth_supported || + !acpi_evalf(hkey_handle, &status, "GBDC", "d")) + status = 0; + + return status; +} + +static int bluetooth_read(char *p) +{ + int len = 0; + int status = bluetooth_status(); + + if (!bluetooth_supported) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!(status & 1)) + len += sprintf(p + len, "status:\t\tnot installed\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int bluetooth_write(char *buf) +{ + int status = bluetooth_status(); + char *cmd; + int do_cmd = 0; + + if (!bluetooth_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status |= 2; + } else if (strlencmp(cmd, "disable") == 0) { + status &= ~2; + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) + return -EIO; + + return 0; +} + +/************************************************************************* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void) +{ + wan_supported = hkey_handle && + acpi_evalf(hkey_handle, NULL, "GWAN", "qv"); + + return 0; +} + +static int wan_status(void) +{ + int status; + + if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d")) + status = 0; + + return status; +} + +static int wan_read(char *p) +{ + int len = 0; + int status = wan_status(); + + if (!wan_supported) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!(status & 1)) + len += sprintf(p + len, "status:\t\tnot installed\n"); + else { + len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 1)); + len += sprintf(p + len, "commands:\tenable, disable\n"); + } + + return len; +} + +static int wan_write(char *buf) +{ + int status = wan_status(); + char *cmd; + int do_cmd = 0; + + if (!wan_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "enable") == 0) { + status |= 2; + } else if (strlencmp(cmd, "disable") == 0) { + status &= ~2; + } else + return -EINVAL; + do_cmd = 1; + } + + if (do_cmd && !acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) + return -EIO; + + return 0; +} + +/************************************************************************* + * Video subdriver + */ + +static enum video_access_mode video_supported; +static int video_orig_autosw; + +IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ + "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */ + "\\_SB.PCI0.VID0", /* 770e */ + "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */ + "\\_SB.PCI0.AGP.VID", /* all others */ + ); /* R30, R31 */ + +IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ + +static int video_init(void) +{ + int ivga; + + if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) + /* G41, assume IVGA doesn't change */ + vid_handle = vid2_handle; + + if (!vid_handle) + /* video switching not supported on R30, R31 */ + video_supported = IBMACPI_VIDEO_NONE; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd")) + /* 570 */ + video_supported = IBMACPI_VIDEO_570; + else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd")) + /* 600e/x, 770e, 770x */ + video_supported = IBMACPI_VIDEO_770; + else + /* all others */ + video_supported = IBMACPI_VIDEO_NEW; + + return 0; +} + +static void video_exit(void) +{ + acpi_evalf(vid_handle, NULL, "_DOS", "vd", video_orig_autosw); +} + +static int video_status(void) +{ + int status = 0; + int i; + + if (video_supported == IBMACPI_VIDEO_570) { + if (acpi_evalf(NULL, &i, "\\_SB.PHS", "dd", 0x87)) + status = i & 3; + } else if (video_supported == IBMACPI_VIDEO_770) { + if (acpi_evalf(NULL, &i, "\\VCDL", "d")) + status |= 0x01 * i; + if (acpi_evalf(NULL, &i, "\\VCDC", "d")) + status |= 0x02 * i; + } else if (video_supported == IBMACPI_VIDEO_NEW) { + acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1); + if (acpi_evalf(NULL, &i, "\\VCDC", "d")) + status |= 0x02 * i; + + acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0); + if (acpi_evalf(NULL, &i, "\\VCDL", "d")) + status |= 0x01 * i; + if (acpi_evalf(NULL, &i, "\\VCDD", "d")) + status |= 0x08 * i; + } + + return status; +} + +static int video_autosw(void) +{ + int autosw = 0; + + if (video_supported == IBMACPI_VIDEO_570) + acpi_evalf(vid_handle, &autosw, "SWIT", "d"); + else if (video_supported == IBMACPI_VIDEO_770 || + video_supported == IBMACPI_VIDEO_NEW) + acpi_evalf(vid_handle, &autosw, "^VDEE", "d"); + + return autosw & 1; +} + +static int video_switch(void) +{ + int autosw = video_autosw(); + int ret; + + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) + return -EIO; + ret = video_supported == IBMACPI_VIDEO_570 ? + acpi_evalf(ec_handle, NULL, "_Q16", "v") : + acpi_evalf(vid_handle, NULL, "VSWT", "v"); + acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); + + return ret; +} + +static int video_expand(void) +{ + if (video_supported == IBMACPI_VIDEO_570) + return acpi_evalf(ec_handle, NULL, "_Q17", "v"); + else if (video_supported == IBMACPI_VIDEO_770) + return acpi_evalf(vid_handle, NULL, "VEXP", "v"); + else + return acpi_evalf(NULL, NULL, "\\VEXP", "v"); +} + +static int video_switch2(int status) +{ + int ret; + + if (video_supported == IBMACPI_VIDEO_570) { + ret = acpi_evalf(NULL, NULL, + "\\_SB.PHS2", "vdd", 0x8b, status | 0x80); + } else if (video_supported == IBMACPI_VIDEO_770) { + int autosw = video_autosw(); + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) + return -EIO; + + ret = acpi_evalf(vid_handle, NULL, + "ASWT", "vdd", status * 0x100, 0); + + acpi_evalf(vid_handle, NULL, "_DOS", "vd", autosw); + } else { + ret = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && + acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); + } + + return ret; +} + +static int video_read(char *p) +{ + int status = video_status(); + int autosw = video_autosw(); + int len = 0; + + if (!video_supported) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); + len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); + if (video_supported == IBMACPI_VIDEO_NEW) + len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); + len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); + len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); + len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); + if (video_supported == IBMACPI_VIDEO_NEW) + len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); + len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); + len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); + + return len; +} + +static int video_write(char *buf) +{ + char *cmd; + int enable, disable, status; + + if (!video_supported) + return -ENODEV; + + enable = disable = 0; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "lcd_enable") == 0) { + enable |= 0x01; + } else if (strlencmp(cmd, "lcd_disable") == 0) { + disable |= 0x01; + } else if (strlencmp(cmd, "crt_enable") == 0) { + enable |= 0x02; + } else if (strlencmp(cmd, "crt_disable") == 0) { + disable |= 0x02; + } else if (video_supported == IBMACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_enable") == 0) { + enable |= 0x08; + } else if (video_supported == IBMACPI_VIDEO_NEW && + strlencmp(cmd, "dvi_disable") == 0) { + disable |= 0x08; + } else if (strlencmp(cmd, "auto_enable") == 0) { + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 1)) + return -EIO; + } else if (strlencmp(cmd, "auto_disable") == 0) { + if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", 0)) + return -EIO; + } else if (strlencmp(cmd, "video_switch") == 0) { + if (!video_switch()) + return -EIO; + } else if (strlencmp(cmd, "expand_toggle") == 0) { + if (!video_expand()) + return -EIO; + } else + return -EINVAL; + } + + if (enable || disable) { + status = (video_status() & 0x0f & ~disable) | enable; + if (!video_switch2(status)) + return -EIO; + } + + return 0; +} + +/************************************************************************* + * Light (thinklight) subdriver + */ + +static int light_supported; +static int light_status_supported; + +IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ +IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ + +static int light_init(void) +{ + /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ + light_supported = (cmos_handle || lght_handle) && !ledb_handle; + + if (light_supported) + /* light status not supported on + 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */ + light_status_supported = acpi_evalf(ec_handle, NULL, + "KBLT", "qv"); + + return 0; +} + +static int light_read(char *p) +{ + int len = 0; + int status = 0; + + if (!light_supported) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + } else if (!light_status_supported) { + len += sprintf(p + len, "status:\t\tunknown\n"); + len += sprintf(p + len, "commands:\ton, off\n"); + } else { + if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) + return -EIO; + len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); + len += sprintf(p + len, "commands:\ton, off\n"); + } + + return len; +} + +static int light_write(char *buf) +{ + int cmos_cmd, lght_cmd; + char *cmd; + int success; + + if (!light_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "on") == 0) { + cmos_cmd = 0x0c; + lght_cmd = 1; + } else if (strlencmp(cmd, "off") == 0) { + cmos_cmd = 0x0d; + lght_cmd = 0; + } else + return -EINVAL; + + success = cmos_handle ? + acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : + acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); + if (!success) + return -EIO; + } + + return 0; +} + +/************************************************************************* + * Dock subdriver + */ + +/* don't list other alternatives as we install a notify handler on the 570 */ +IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ + +#ifdef CONFIG_ACPI_IBM_DOCK + +IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ + "\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */ + "\\_SB.PCI0.PCI1.DOCK", /* all others */ + "\\_SB.PCI.ISA.SLCE", /* 570 */ + ); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */ + +#define dock_docked() (_sta(dock_handle) & 1) + +static void dock_notify(struct ibm_struct *ibm, u32 event) +{ + int docked = dock_docked(); + int pci = ibm->hid && strstr(ibm->hid, IBM_PCI_HID); + + if (event == 1 && !pci) /* 570 */ + acpi_bus_generate_event(ibm->device, event, 1); /* button */ + else if (event == 1 && pci) /* 570 */ + acpi_bus_generate_event(ibm->device, event, 3); /* dock */ + else if (event == 3 && docked) + acpi_bus_generate_event(ibm->device, event, 1); /* button */ + else if (event == 3 && !docked) + acpi_bus_generate_event(ibm->device, event, 2); /* undock */ + else if (event == 0 && docked) + acpi_bus_generate_event(ibm->device, event, 3); /* dock */ + else { + printk(IBM_ERR "unknown dock event %d, status %d\n", + event, _sta(dock_handle)); + acpi_bus_generate_event(ibm->device, event, 0); /* unknown */ + } +} + +static int dock_read(char *p) +{ + int len = 0; + int docked = dock_docked(); + + if (!dock_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else if (!docked) + len += sprintf(p + len, "status:\t\tundocked\n"); + else { + len += sprintf(p + len, "status:\t\tdocked\n"); + len += sprintf(p + len, "commands:\tdock, undock\n"); + } + + return len; +} + +static int dock_write(char *buf) +{ + char *cmd; + + if (!dock_docked()) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "undock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) || + !acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1)) + return -EIO; + } else if (strlencmp(cmd, "dock") == 0) { + if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +#endif /* CONFIG_ACPI_IBM_DOCK */ + +/************************************************************************* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_BAY +static int bay_status_supported; +static int bay_status2_supported; +static int bay_eject_supported; +static int bay_eject2_supported; + +IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ + "\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */ + "\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */ + "\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */ + ); /* A21e, R30, R31 */ +IBM_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */ + "_EJ0", /* all others */ + ); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */ +IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */ + "\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */ + ); /* all others */ +IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ + "_EJ0", /* 770x */ + ); /* all others */ + +static int bay_init(void) +{ + bay_status_supported = bay_handle && + acpi_evalf(bay_handle, NULL, "_STA", "qv"); + bay_status2_supported = bay2_handle && + acpi_evalf(bay2_handle, NULL, "_STA", "qv"); + + bay_eject_supported = bay_handle && bay_ej_handle && + (strlencmp(bay_ej_path, "_EJ0") == 0 || experimental); + bay_eject2_supported = bay2_handle && bay2_ej_handle && + (strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental); + + return 0; +} + +static void bay_notify(struct ibm_struct *ibm, u32 event) +{ + acpi_bus_generate_event(ibm->device, event, 0); +} + +#define bay_occupied(b) (_sta(b##_handle) & 1) + +static int bay_read(char *p) +{ + int len = 0; + int occupied = bay_occupied(bay); + int occupied2 = bay_occupied(bay2); + int eject, eject2; + + len += sprintf(p + len, "status:\t\t%s\n", bay_status_supported ? + (occupied ? "occupied" : "unoccupied") : + "not supported"); + if (bay_status2_supported) + len += sprintf(p + len, "status2:\t%s\n", occupied2 ? + "occupied" : "unoccupied"); + + eject = bay_eject_supported && occupied; + eject2 = bay_eject2_supported && occupied2; + + if (eject && eject2) + len += sprintf(p + len, "commands:\teject, eject2\n"); + else if (eject) + len += sprintf(p + len, "commands:\teject\n"); + else if (eject2) + len += sprintf(p + len, "commands:\teject2\n"); + + return len; +} + +static int bay_write(char *buf) +{ + char *cmd; + + if (!bay_eject_supported && !bay_eject2_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (bay_eject_supported && strlencmp(cmd, "eject") == 0) { + if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1)) + return -EIO; + } else if (bay_eject2_supported && + strlencmp(cmd, "eject2") == 0) { + if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_ACPI_IBM_BAY */ + +/************************************************************************* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd) +{ + if (cmos_handle) + return acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd); + else + return 1; +} + +static int cmos_read(char *p) +{ + int len = 0; + + /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, + R30, R31, T20-22, X20-21 */ + if (!cmos_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "commands:\t ( is 0-21)\n"); + } + + return len; +} + +static int cmos_write(char *buf) +{ + char *cmd; + int cmos_cmd; + + if (!cmos_handle) + return -EINVAL; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &cmos_cmd) == 1 && + cmos_cmd >= 0 && cmos_cmd <= 21) { + /* cmos_cmd set */ + } else + return -EINVAL; + + if (!cmos_eval(cmos_cmd)) + return -EIO; + } + + return 0; +} + + +/************************************************************************* + * LED subdriver + */ + +static enum led_access_mode led_supported; + +IBM_HANDLE(led, ec, "SLED", /* 570 */ + "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + "LED", /* all others */ + ); /* R30, R31 */ + +static int led_init(void) +{ + if (!led_handle) + /* led not supported on R30, R31 */ + led_supported = IBMACPI_LED_NONE; + else if (strlencmp(led_path, "SLED") == 0) + /* 570 */ + led_supported = IBMACPI_LED_570; + else if (strlencmp(led_path, "SYSL") == 0) + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + led_supported = IBMACPI_LED_OLD; + else + /* all others */ + led_supported = IBMACPI_LED_NEW; + + return 0; +} + +#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) + +static int led_read(char *p) +{ + int len = 0; + + if (!led_supported) { + len += sprintf(p + len, "status:\t\tnot supported\n"); + return len; + } + len += sprintf(p + len, "status:\t\tsupported\n"); + + if (led_supported == IBMACPI_LED_570) { + /* 570 */ + int i, status; + for (i = 0; i < 8; i++) { + if (!acpi_evalf(ec_handle, + &status, "GLED", "dd", 1 << i)) + return -EIO; + len += sprintf(p + len, "%d:\t\t%s\n", + i, led_status(status)); + } + } + + len += sprintf(p + len, "commands:\t" + " on, off, blink ( is 0-7)\n"); + + return len; +} + +/* off, on, blink */ +static const int led_sled_arg1[] = { 0, 1, 3 }; +static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ +static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ +static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; + +static int led_write(char *buf) +{ + char *cmd; + int led, ind, ret; + + if (!led_supported) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7) + return -EINVAL; + + if (strstr(cmd, "off")) { + ind = 0; + } else if (strstr(cmd, "on")) { + ind = 1; + } else if (strstr(cmd, "blink")) { + ind = 2; + } else + return -EINVAL; + + if (led_supported == IBMACPI_LED_570) { + /* 570 */ + led = 1 << led; + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_sled_arg1[ind])) + return -EIO; + } else if (led_supported == IBMACPI_LED_OLD) { + /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ + led = 1 << led; + ret = ec_write(IBMACPI_LED_EC_HLMS, led); + if (ret >= 0) + ret = + ec_write(IBMACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); + if (ret >= 0) + ret = + ec_write(IBMACPI_LED_EC_HLCL, + led * led_exp_hlcl[ind]); + if (ret < 0) + return ret; + } else { + /* all others */ + if (!acpi_evalf(led_handle, NULL, NULL, "vdd", + led, led_led_arg1[ind])) + return -EIO; + } + } + + return 0; +} + +/************************************************************************* + * Beep subdriver + */ + +IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ + +static int beep_read(char *p) +{ + int len = 0; + + if (!beep_handle) + len += sprintf(p + len, "status:\t\tnot supported\n"); + else { + len += sprintf(p + len, "status:\t\tsupported\n"); + len += sprintf(p + len, "commands:\t ( is 0-17)\n"); + } + + return len; +} + +static int beep_write(char *buf) +{ + char *cmd; + int beep_cmd; + + if (!beep_handle) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "%u", &beep_cmd) == 1 && + beep_cmd >= 0 && beep_cmd <= 17) { + /* beep_cmd set */ + } else + return -EINVAL; + if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0)) + return -EIO; + } + + return 0; +} + +/************************************************************************* + * Thermal subdriver + */ + +static enum thermal_access_mode thermal_read_mode; + +static int thermal_init(void) +{ + u8 t, ta1, ta2; + int i; + int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv"); + + if (ibm_thinkpad_ec_found && experimental) { + /* + * Direct EC access mode: sensors at registers + * 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for + * non-implemented, thermal sensors return 0x80 when + * not available + */ + + ta1 = ta2 = 0; + for (i = 0; i < 8; i++) { + if (likely(acpi_ec_read(0x78 + i, &t))) { + ta1 |= t; + } else { + ta1 = 0; + break; + } + if (likely(acpi_ec_read(0xC0 + i, &t))) { + ta2 |= t; + } else { + ta1 = 0; + break; + } + } + if (ta1 == 0) { + /* This is sheer paranoia, but we handle it anyway */ + if (acpi_tmp7) { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "falling back to ACPI TMPx access mode\n"); + thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "disabling thermal sensors access\n"); + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + } else { + thermal_read_mode = + (ta2 != 0) ? + IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8; + } + } else if (acpi_tmp7) { + if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) { + /* 600e/x, 770e, 770x */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT; + } else { + /* Standard ACPI TMPx access, max 8 sensors */ + thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07; + } + } else { + /* temperatures not supported on 570, G4x, R30, R31, R32 */ + thermal_read_mode = IBMACPI_THERMAL_NONE; + } + + return 0; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int i, t; + s8 tmp; + char tmpi[] = "TMPi"; + + if (!s) + return -EINVAL; + + switch (thermal_read_mode) { +#if IBMACPI_MAX_THERMAL_SENSORS >= 16 + case IBMACPI_THERMAL_TPEC_16: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0xC0 + i, &tmp)) + return -EIO; + s->temp[i + 8] = tmp * 1000; + } + /* fallthrough */ +#endif + case IBMACPI_THERMAL_TPEC_8: + for (i = 0; i < 8; i++) { + if (!acpi_ec_read(0x78 + i, &tmp)) + return -EIO; + s->temp[i] = tmp * 1000; + } + return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8; + + case IBMACPI_THERMAL_ACPI_UPDT: + if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) + return -EIO; + for (i = 0; i < 8; i++) { + tmpi[3] = '0' + i; + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + s->temp[i] = (t - 2732) * 100; + } + return 8; + + case IBMACPI_THERMAL_ACPI_TMP07: + for (i = 0; i < 8; i++) { + tmpi[3] = '0' + i; + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + s->temp[i] = t * 1000; + } + return 8; + + case IBMACPI_THERMAL_NONE: + default: + return 0; + } +} + +static int thermal_read(char *p) +{ + int len = 0; + int n, i; + struct ibm_thermal_sensors_struct t; + + n = thermal_get_sensors(&t); + if (unlikely(n < 0)) + return n; + + len += sprintf(p + len, "temperatures:\t"); + + if (n > 0) { + for (i = 0; i < (n - 1); i++) + len += sprintf(p + len, "%d ", t.temp[i] / 1000); + len += sprintf(p + len, "%d\n", t.temp[i] / 1000); + } else + len += sprintf(p + len, "not supported\n"); + + return len; +} + +/************************************************************************* + * EC Dump subdriver + */ + +static u8 ecdump_regs[256]; + +static int ecdump_read(char *p) +{ + int len = 0; + int i, j; + u8 v; + + len += sprintf(p + len, "EC " + " +00 +01 +02 +03 +04 +05 +06 +07" + " +08 +09 +0a +0b +0c +0d +0e +0f\n"); + for (i = 0; i < 256; i += 16) { + len += sprintf(p + len, "EC 0x%02x:", i); + for (j = 0; j < 16; j++) { + if (!acpi_ec_read(i + j, &v)) + break; + if (v != ecdump_regs[i + j]) + len += sprintf(p + len, " *%02x", v); + else + len += sprintf(p + len, " %02x", v); + ecdump_regs[i + j] = v; + } + len += sprintf(p + len, "\n"); + if (j != 16) + break; + } + + /* These are way too dangerous to advertise openly... */ +#if 0 + len += sprintf(p + len, "commands:\t0x 0x" + " ( is 00-ff, is 00-ff)\n"); + len += sprintf(p + len, "commands:\t0x " + " ( is 00-ff, is 0-255)\n"); +#endif + return len; +} + +static int ecdump_write(char *buf) +{ + char *cmd; + int i, v; + + while ((cmd = next_cmd(&buf))) { + if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) { + /* i and v set */ + } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) { + /* i and v set */ + } else + return -EINVAL; + if (i >= 0 && i < 256 && v >= 0 && v < 256) { + if (!acpi_ec_write(i, v)) + return -EIO; + } else + return -EINVAL; + } + + return 0; +} + +/************************************************************************* + * Backlight/brightness subdriver + */ + +static struct backlight_device *ibm_backlight_device = NULL; + +static struct backlight_ops ibm_backlight_data = { + .get_brightness = brightness_get, + .update_status = brightness_update_status, +}; + +static int brightness_init(void) +{ + int b; + + b = brightness_get(NULL); + if (b < 0) + return b; + + ibm_backlight_device = backlight_device_register("ibm", NULL, NULL, + &ibm_backlight_data); + if (IS_ERR(ibm_backlight_device)) { + printk(IBM_ERR "Could not register backlight device\n"); + return PTR_ERR(ibm_backlight_device); + } + + ibm_backlight_device->props.max_brightness = 7; + ibm_backlight_device->props.brightness = b; + backlight_update_status(ibm_backlight_device); + + return 0; +} + +static void brightness_exit(void) +{ + if (ibm_backlight_device) { + backlight_device_unregister(ibm_backlight_device); + ibm_backlight_device = NULL; + } +} + +static int brightness_update_status(struct backlight_device *bd) +{ + return brightness_set( + (bd->props.fb_blank == FB_BLANK_UNBLANK && + bd->props.power == FB_BLANK_UNBLANK) ? + bd->props.brightness : 0); +} + +static int brightness_get(struct backlight_device *bd) +{ + u8 level; + if (!acpi_ec_read(brightness_offset, &level)) + return -EIO; + + level &= 0x7; + + return level; +} + +static int brightness_set(int value) +{ + int cmos_cmd, inc, i; + int current_value = brightness_get(NULL); + + value &= 7; + + cmos_cmd = value > current_value ? TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN; + inc = value > current_value ? 1 : -1; + for (i = current_value; i != value; i += inc) { + if (!cmos_eval(cmos_cmd)) + return -EIO; + if (!acpi_ec_write(brightness_offset, i + inc)) + return -EIO; + } + + return 0; +} + +static int brightness_read(char *p) +{ + int len = 0; + int level; + + if ((level = brightness_get(NULL)) < 0) { + len += sprintf(p + len, "level:\t\tunreadable\n"); + } else { + len += sprintf(p + len, "level:\t\t%d\n", level & 0x7); + len += sprintf(p + len, "commands:\tup, down\n"); + len += sprintf(p + len, "commands:\tlevel " + " ( is 0-7)\n"); + } + + return len; +} + +static int brightness_write(char *buf) +{ + int level; + int new_level; + char *cmd; + + while ((cmd = next_cmd(&buf))) { + if ((level = brightness_get(NULL)) < 0) + return level; + level &= 7; + + if (strlencmp(cmd, "up") == 0) { + new_level = level == 7 ? 7 : level + 1; + } else if (strlencmp(cmd, "down") == 0) { + new_level = level == 0 ? 0 : level - 1; + } else if (sscanf(cmd, "level %d", &new_level) == 1 && + new_level >= 0 && new_level <= 7) { + /* new_level set */ + } else + return -EINVAL; + + brightness_set(new_level); + } + + return 0; +} + +/************************************************************************* + * Volume subdriver + */ + +static int volume_read(char *p) +{ + int len = 0; + u8 level; + + if (!acpi_ec_read(volume_offset, &level)) { + len += sprintf(p + len, "level:\t\tunreadable\n"); + } else { + len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); + len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); + len += sprintf(p + len, "commands:\tup, down, mute\n"); + len += sprintf(p + len, "commands:\tlevel " + " ( is 0-15)\n"); + } + + return len; +} + +static int volume_write(char *buf) +{ + int cmos_cmd, inc, i; + u8 level, mute; + int new_level, new_mute; + char *cmd; + + while ((cmd = next_cmd(&buf))) { + if (!acpi_ec_read(volume_offset, &level)) + return -EIO; + new_mute = mute = level & 0x40; + new_level = level = level & 0xf; + + if (strlencmp(cmd, "up") == 0) { + if (mute) + new_mute = 0; + else + new_level = level == 15 ? 15 : level + 1; + } else if (strlencmp(cmd, "down") == 0) { + if (mute) + new_mute = 0; + else + new_level = level == 0 ? 0 : level - 1; + } else if (sscanf(cmd, "level %d", &new_level) == 1 && + new_level >= 0 && new_level <= 15) { + /* new_level set */ + } else if (strlencmp(cmd, "mute") == 0) { + new_mute = 0x40; + } else + return -EINVAL; + + if (new_level != level) { /* mute doesn't change */ + cmos_cmd = new_level > level ? TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; + inc = new_level > level ? 1 : -1; + + if (mute && (!cmos_eval(cmos_cmd) || + !acpi_ec_write(volume_offset, level))) + return -EIO; + + for (i = level; i != new_level; i += inc) + if (!cmos_eval(cmos_cmd) || + !acpi_ec_write(volume_offset, i + inc)) + return -EIO; + + if (mute && (!cmos_eval(TP_CMOS_VOLUME_MUTE) || + !acpi_ec_write(volume_offset, + new_level + mute))) + return -EIO; + } + + if (new_mute != mute) { /* level doesn't change */ + cmos_cmd = new_mute ? TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; + + if (!cmos_eval(cmos_cmd) || + !acpi_ec_write(volume_offset, level + new_mute)) + return -EIO; + } + } + + return 0; +} + + +/************************************************************************* + * Fan subdriver + */ + +/* + * FAN ACCESS MODES + * + * IBMACPI_FAN_RD_ACPI_GFAN: + * ACPI GFAN method: returns fan level + * + * see IBMACPI_FAN_WR_ACPI_SFAN + * EC 0x2f not available if GFAN exists + * + * IBMACPI_FAN_WR_ACPI_SFAN: + * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max) + * + * EC 0x2f might be available *for reading*, but never for writing. + * + * IBMACPI_FAN_WR_TPEC: + * ThinkPad EC register 0x2f (HFSP): fan control loop mode Supported + * on almost all ThinkPads + * + * Fan speed changes of any sort (including those caused by the + * disengaged mode) are usually done slowly by the firmware as the + * maximum ammount of fan duty cycle change per second seems to be + * limited. + * + * Reading is not available if GFAN exists. + * Writing is not available if SFAN exists. + * + * Bits + * 7 automatic mode engaged; + * (default operation mode of the ThinkPad) + * fan level is ignored in this mode. + * 6 disengage mode (takes precedence over bit 7); + * not available on all thinkpads. May disable + * the tachometer, and speeds up fan to 100% duty-cycle, + * which speeds it up far above the standard RPM + * levels. It is not impossible that it could cause + * hardware damage. + * 5-3 unused in some models. Extra bits for fan level + * in others, but still useless as all values above + * 7 map to the same speed as level 7 in these models. + * 2-0 fan level (0..7 usually) + * 0x00 = stop + * 0x07 = max (set when temperatures critical) + * Some ThinkPads may have other levels, see + * IBMACPI_FAN_WR_ACPI_FANS (X31/X40/X41) + * + * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at + * boot. Apparently the EC does not intialize it, so unless ACPI DSDT + * does so, its initial value is meaningless (0x07). + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * ---- + * + * ThinkPad EC register 0x84 (LSB), 0x85 (MSB): + * Main fan tachometer reading (in RPM) + * + * This register is present on all ThinkPads with a new-style EC, and + * it is known not to be present on the A21m/e, and T22, as there is + * something else in offset 0x84 according to the ACPI DSDT. Other + * ThinkPads from this same time period (and earlier) probably lack the + * tachometer as well. + * + * Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare + * was never fixed by IBM to report the EC firmware version string + * probably support the tachometer (like the early X models), so + * detecting it is quite hard. We need more data to know for sure. + * + * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings + * might result. + * + * FIRMWARE BUG: when EC 0x2f bit 6 is set (disengaged mode), this + * register is not invalidated in ThinkPads that disable tachometer + * readings. Thus, the tachometer readings go stale. + * + * For firmware bugs, refer to: + * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues + * + * IBMACPI_FAN_WR_ACPI_FANS: + * ThinkPad X31, X40, X41. Not available in the X60. + * + * FANS ACPI handle: takes three arguments: low speed, medium speed, + * high speed. ACPI DSDT seems to map these three speeds to levels + * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH + * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3") + * + * The speeds are stored on handles + * (FANA:FAN9), (FANC:FANB), (FANE:FAND). + * + * There are three default speed sets, acessible as handles: + * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H + * + * ACPI DSDT switches which set is in use depending on various + * factors. + * + * IBMACPI_FAN_WR_TPEC is also available and should be used to + * command the fan. The X31/X40/X41 seems to have 8 fan levels, + * but the ACPI tables just mention level 7. + */ + +static enum fan_status_access_mode fan_status_access_mode; +static enum fan_control_access_mode fan_control_access_mode; +static enum fan_control_commands fan_control_commands; + +static int fan_control_status_known; +static u8 fan_control_initial_status; + +static void fan_watchdog_fire(struct work_struct *ignored); +static int fan_watchdog_maxinterval; +static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); + +IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ +IBM_HANDLE(gfan, ec, "GFAN", /* 570 */ + "\\FSPD", /* 600e/x, 770e, 770x */ + ); /* all others */ +IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ + "JFNS", /* 770x-JL */ + ); /* all others */ + +static int fan_init(void) +{ + fan_status_access_mode = IBMACPI_FAN_NONE; + fan_control_access_mode = IBMACPI_FAN_WR_NONE; + fan_control_commands = 0; + fan_control_status_known = 1; + fan_watchdog_maxinterval = 0; + + if (gfan_handle) { + /* 570, 600e/x, 770e, 770x */ + fan_status_access_mode = IBMACPI_FAN_RD_ACPI_GFAN; + } else { + /* all other ThinkPads: note that even old-style + * ThinkPad ECs supports the fan control register */ + if (likely(acpi_ec_read(fan_status_offset, + &fan_control_initial_status))) { + fan_status_access_mode = IBMACPI_FAN_RD_TPEC; + + /* In some ThinkPads, neither the EC nor the ACPI + * DSDT initialize the fan status, and it ends up + * being set to 0x07 when it *could* be either + * 0x07 or 0x80. + * + * Enable for TP-1Y (T43), TP-78 (R51e), + * TP-76 (R52), TP-70 (T43, R52), which are known + * to be buggy. */ + if (fan_control_initial_status == 0x07 && + ibm_thinkpad_ec_found && + ((ibm_thinkpad_ec_found[0] == '1' && + ibm_thinkpad_ec_found[1] == 'Y') || + (ibm_thinkpad_ec_found[0] == '7' && + (ibm_thinkpad_ec_found[1] == '6' || + ibm_thinkpad_ec_found[1] == '8' || + ibm_thinkpad_ec_found[1] == '0')) + )) { + printk(IBM_NOTICE + "fan_init: initial fan status is " + "unknown, assuming it is in auto " + "mode\n"); + fan_control_status_known = 0; + } + } else { + printk(IBM_ERR + "ThinkPad ACPI EC access misbehaving, " + "fan status and control unavailable\n"); + return 0; + } + } + + if (sfan_handle) { + /* 570, 770x-JL */ + fan_control_access_mode = IBMACPI_FAN_WR_ACPI_SFAN; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | IBMACPI_FAN_CMD_ENABLE; + } else { + if (!gfan_handle) { + /* gfan without sfan means no fan control */ + /* all other models implement TP EC 0x2f control */ + + if (fans_handle) { + /* X31, X40, X41 */ + fan_control_access_mode = + IBMACPI_FAN_WR_ACPI_FANS; + fan_control_commands |= + IBMACPI_FAN_CMD_SPEED | + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } else { + fan_control_access_mode = IBMACPI_FAN_WR_TPEC; + fan_control_commands |= + IBMACPI_FAN_CMD_LEVEL | + IBMACPI_FAN_CMD_ENABLE; + } + } + } + + return 0; +} + +static int fan_get_status(u8 *status) +{ + u8 s; + + /* TODO: + * Add IBMACPI_FAN_RD_ACPI_FANS ? */ + + switch (fan_status_access_mode) { + case IBMACPI_FAN_RD_ACPI_GFAN: + /* 570, 600e/x, 770e, 770x */ + + if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) + return -EIO; + + if (likely(status)) + *status = s & 0x07; + + break; + + case IBMACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!acpi_ec_read(fan_status_offset, &s))) + return -EIO; + + if (likely(status)) + *status = s; + + break; + + default: + return -ENXIO; + } + + return 0; +} + +static void fan_exit(void) +{ + cancel_delayed_work(&fan_watchdog_task); + flush_scheduled_work(); +} + +static int fan_get_speed(unsigned int *speed) +{ + u8 hi, lo; + + switch (fan_status_access_mode) { + case IBMACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) || + !acpi_ec_read(fan_rpm_offset + 1, &hi))) + return -EIO; + + if (likely(speed)) + *speed = (hi << 8) | lo; + + break; + + default: + return -ENXIO; + } + + return 0; +} + +static void fan_watchdog_fire(struct work_struct *ignored) +{ + printk(IBM_NOTICE "fan watchdog: enabling fan\n"); + if (fan_set_enable()) { + printk(IBM_ERR "fan watchdog: error while enabling fan\n"); + /* reschedule for later */ + fan_watchdog_reset(); + } +} + +static void fan_watchdog_reset(void) +{ + static int fan_watchdog_active = 0; + + if (fan_watchdog_active) + cancel_delayed_work(&fan_watchdog_task); + + if (fan_watchdog_maxinterval > 0) { + fan_watchdog_active = 1; + if (!schedule_delayed_work(&fan_watchdog_task, + msecs_to_jiffies(fan_watchdog_maxinterval + * 1000))) { + printk(IBM_ERR "failed to schedule the fan watchdog, " + "watchdog will not trigger\n"); + } + } else + fan_watchdog_active = 0; +} + +static int fan_set_level(int level) +{ + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_SFAN: + if (level >= 0 && level <= 7) { + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level)) + return -EIO; + } else + return -EINVAL; + break; + + case IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((level != IBMACPI_FAN_EC_AUTO) && + (level != IBMACPI_FAN_EC_DISENGAGED) && + ((level < 0) || (level > 7))) + return -EINVAL; + + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + else + fan_control_status_known = 1; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_set_enable(void) +{ + u8 s; + int rc; + + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if ((rc = fan_get_status(&s)) < 0) + return rc; + + /* Don't go out of emergency fan mode */ + if (s != 7) + s = IBMACPI_FAN_EC_AUTO; + + if (!acpi_ec_write(fan_status_offset, s)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_FAN_WR_ACPI_SFAN: + if ((rc = fan_get_status(&s)) < 0) + return rc; + + s &= 0x07; + + /* Set fan to at least level 4 */ + if (s < 4) + s = 4; + + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) + return -EIO; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_set_disable(void) +{ + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_FANS: + case IBMACPI_FAN_WR_TPEC: + if (!acpi_ec_write(fan_status_offset, 0x00)) + return -EIO; + else + fan_control_status_known = 1; + break; + + case IBMACPI_FAN_WR_ACPI_SFAN: + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) + return -EIO; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_set_speed(int speed) +{ + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_FANS: + if (speed >= 0 && speed <= 65535) { + if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", + speed, speed, speed)) + return -EIO; + } else + return -EINVAL; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_read(char *p) +{ + int len = 0; + int rc; + u8 status; + unsigned int speed = 0; + + switch (fan_status_access_mode) { + case IBMACPI_FAN_RD_ACPI_GFAN: + /* 570, 600e/x, 770e, 770x */ + if ((rc = fan_get_status(&status)) < 0) + return rc; + + len += sprintf(p + len, "status:\t\t%s\n" + "level:\t\t%d\n", + (status != 0) ? "enabled" : "disabled", status); + break; + + case IBMACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if ((rc = fan_get_status(&status)) < 0) + return rc; + + if (unlikely(!fan_control_status_known)) { + if (status != fan_control_initial_status) + fan_control_status_known = 1; + else + /* Return most likely status. In fact, it + * might be the only possible status */ + status = IBMACPI_FAN_EC_AUTO; + } + + len += sprintf(p + len, "status:\t\t%s\n", + (status != 0) ? "enabled" : "disabled"); + + /* No ThinkPad boots on disengaged mode, we can safely + * assume the tachometer is online if fan control status + * was unknown */ + if ((rc = fan_get_speed(&speed)) < 0) + return rc; + + len += sprintf(p + len, "speed:\t\t%d\n", speed); + + if (status & IBMACPI_FAN_EC_DISENGAGED) + /* Disengaged mode takes precedence */ + len += sprintf(p + len, "level:\t\tdisengaged\n"); + else if (status & IBMACPI_FAN_EC_AUTO) + len += sprintf(p + len, "level:\t\tauto\n"); + else + len += sprintf(p + len, "level:\t\t%d\n", status); + break; + + case IBMACPI_FAN_NONE: + default: + len += sprintf(p + len, "status:\t\tnot supported\n"); + } + + if (fan_control_commands & IBMACPI_FAN_CMD_LEVEL) { + len += sprintf(p + len, "commands:\tlevel "); + + switch (fan_control_access_mode) { + case IBMACPI_FAN_WR_ACPI_SFAN: + len += sprintf(p + len, " ( is 0-7)\n"); + break; + + default: + len += sprintf(p + len, " ( is 0-7, " + "auto, disengaged)\n"); + break; + } + } + + if (fan_control_commands & IBMACPI_FAN_CMD_ENABLE) + len += sprintf(p + len, "commands:\tenable, disable\n" + "commands:\twatchdog ( is 0 (off), " + "1-120 (seconds))\n"); + + if (fan_control_commands & IBMACPI_FAN_CMD_SPEED) + len += sprintf(p + len, "commands:\tspeed " + " ( is 0-65535)\n"); + + return len; +} + +static int fan_write_cmd_level(const char *cmd, int *rc) +{ + int level; + + if (strlencmp(cmd, "level auto") == 0) + level = IBMACPI_FAN_EC_AUTO; + else if (strlencmp(cmd, "level disengaged") == 0) + level = IBMACPI_FAN_EC_DISENGAGED; + else if (sscanf(cmd, "level %d", &level) != 1) + return 0; + + if ((*rc = fan_set_level(level)) == -ENXIO) + printk(IBM_ERR "level command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_enable(const char *cmd, int *rc) +{ + if (strlencmp(cmd, "enable") != 0) + return 0; + + if ((*rc = fan_set_enable()) == -ENXIO) + printk(IBM_ERR "enable command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_disable(const char *cmd, int *rc) +{ + if (strlencmp(cmd, "disable") != 0) + return 0; + + if ((*rc = fan_set_disable()) == -ENXIO) + printk(IBM_ERR "disable command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_speed(const char *cmd, int *rc) +{ + int speed; + + /* TODO: + * Support speed ? */ + + if (sscanf(cmd, "speed %d", &speed) != 1) + return 0; + + if ((*rc = fan_set_speed(speed)) == -ENXIO) + printk(IBM_ERR "speed command accepted for unsupported " + "access mode %d", fan_control_access_mode); + + return 1; +} + +static int fan_write_cmd_watchdog(const char *cmd, int *rc) +{ + int interval; + + if (sscanf(cmd, "watchdog %d", &interval) != 1) + return 0; + + if (interval < 0 || interval > 120) + *rc = -EINVAL; + else + fan_watchdog_maxinterval = interval; + + return 1; +} + +static int fan_write(char *buf) +{ + char *cmd; + int rc = 0; + + while (!rc && (cmd = next_cmd(&buf))) { + if (!((fan_control_commands & IBMACPI_FAN_CMD_LEVEL) && + fan_write_cmd_level(cmd, &rc)) && + !((fan_control_commands & IBMACPI_FAN_CMD_ENABLE) && + (fan_write_cmd_enable(cmd, &rc) || + fan_write_cmd_disable(cmd, &rc) || + fan_write_cmd_watchdog(cmd, &rc))) && + !((fan_control_commands & IBMACPI_FAN_CMD_SPEED) && + fan_write_cmd_speed(cmd, &rc)) + ) + rc = -EINVAL; + else if (!rc) + fan_watchdog_reset(); + } + + return rc; +} + +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ + +/* /proc support */ +static struct proc_dir_entry *proc_dir = NULL; + +/* Subdriver registry */ +static struct ibm_struct ibms[] = { + { + .name = "driver", + .init = ibm_acpi_driver_init, + .read = ibm_acpi_driver_read, + }, + { + .name = "hotkey", + .hid = IBM_HKEY_HID, + .init = hotkey_init, + .read = hotkey_read, + .write = hotkey_write, + .exit = hotkey_exit, + .notify = hotkey_notify, + .handle = &hkey_handle, + .type = ACPI_DEVICE_NOTIFY, + }, + { + .name = "bluetooth", + .init = bluetooth_init, + .read = bluetooth_read, + .write = bluetooth_write, + }, + { + .name = "wan", + .init = wan_init, + .read = wan_read, + .write = wan_write, + .experimental = 1, + }, + { + .name = "video", + .init = video_init, + .read = video_read, + .write = video_write, + .exit = video_exit, + }, + { + .name = "light", + .init = light_init, + .read = light_read, + .write = light_write, + }, +#ifdef CONFIG_ACPI_IBM_DOCK + { + .name = "dock", + .read = dock_read, + .write = dock_write, + .notify = dock_notify, + .handle = &dock_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, + { + .name = "dock", + .hid = IBM_PCI_HID, + .notify = dock_notify, + .handle = &pci_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +#endif +#ifdef CONFIG_ACPI_IBM_BAY + { + .name = "bay", + .init = bay_init, + .read = bay_read, + .write = bay_write, + .notify = bay_notify, + .handle = &bay_handle, + .type = ACPI_SYSTEM_NOTIFY, + }, +#endif /* CONFIG_ACPI_IBM_BAY */ + { + .name = "cmos", + .read = cmos_read, + .write = cmos_write, + }, + { + .name = "led", + .init = led_init, + .read = led_read, + .write = led_write, + }, + { + .name = "beep", + .read = beep_read, + .write = beep_write, + }, + { + .name = "thermal", + .init = thermal_init, + .read = thermal_read, + }, + { + .name = "ecdump", + .read = ecdump_read, + .write = ecdump_write, + .experimental = 1, + }, + { + .name = "brightness", + .read = brightness_read, + .write = brightness_write, + .init = brightness_init, + .exit = brightness_exit, + }, + { + .name = "volume", + .read = volume_read, + .write = volume_write, + }, + { + .name = "fan", + .read = fan_read, + .write = fan_write, + .init = fan_init, + .exit = fan_exit, + .experimental = 1, + }, +}; + +/* + * Module and infrastructure proble, init and exit handling + */ + +static int __init ibm_init(struct ibm_struct *ibm) +{ + int ret; + struct proc_dir_entry *entry; + + if (ibm->experimental && !experimental) + return 0; + + if (ibm->hid) { + ret = register_ibmacpi_subdriver(ibm); + if (ret < 0) + return ret; + ibm->driver_registered = 1; + } + + if (ibm->init) { + ret = ibm->init(); + if (ret != 0) + return ret; + ibm->init_called = 1; + } + + if (ibm->read) { + entry = create_proc_entry(ibm->name, + S_IFREG | S_IRUGO | S_IWUSR, + proc_dir); + if (!entry) { + printk(IBM_ERR "unable to create proc entry %s\n", + ibm->name); + return -ENODEV; + } + entry->owner = THIS_MODULE; + entry->data = ibm; + entry->read_proc = &dispatch_read; + if (ibm->write) + entry->write_proc = &dispatch_write; + ibm->proc_created = 1; + } + + if (ibm->notify) { + ret = setup_notify(ibm); + if (ret == -ENODEV) { + printk(IBM_NOTICE "disabling subdriver %s\n", + ibm->name); + ibm_exit(ibm); + return 0; + } + if (ret < 0) + return ret; + } + + return 0; +} + +static void ibm_exit(struct ibm_struct *ibm) +{ + if (ibm->notify_installed) + acpi_remove_notify_handler(*ibm->handle, ibm->type, + dispatch_notify); + + if (ibm->proc_created) + remove_proc_entry(ibm->name, proc_dir); + + if (ibm->init_called && ibm->exit) + ibm->exit(); + + if (ibm->driver_registered) { + acpi_bus_unregister_driver(ibm->driver); + kfree(ibm->driver); + } +} + +/* Probing */ + +static char *ibm_thinkpad_ec_found = NULL; + +static char* __init check_dmi_for_ec(void) +{ + struct dmi_device *dev = NULL; + char ec_fw_string[18]; + + /* + * ThinkPad T23 or newer, A31 or newer, R50e or newer, + * X32 or newer, all Z series; Some models must have an + * up-to-date BIOS or they will not be detected. + * + * See http://thinkwiki.org/wiki/List_of_DMI_IDs + */ + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { + if (sscanf(dev->name, + "IBM ThinkPad Embedded Controller -[%17c", + ec_fw_string) == 1) { + ec_fw_string[sizeof(ec_fw_string) - 1] = 0; + ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; + return kstrdup(ec_fw_string, GFP_KERNEL); + } + } + return NULL; +} + +/* Module init, exit, parameters */ + +static int __init set_ibm_param(const char *val, struct kernel_param *kp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ibms); i++) + if (strcmp(ibms[i].name, kp->name) == 0 && ibms[i].write) { + if (strlen(val) > sizeof(ibms[i].param) - 2) + return -ENOSPC; + strcpy(ibms[i].param, val); + strcat(ibms[i].param, ","); + return 0; + } + + return -EINVAL; +} + +static int experimental; +module_param(experimental, int, 0); + +#define IBM_PARAM(feature) \ + module_param_call(feature, set_ibm_param, NULL, NULL, 0) + +IBM_PARAM(hotkey); +IBM_PARAM(bluetooth); +IBM_PARAM(video); +IBM_PARAM(light); +#ifdef CONFIG_ACPI_IBM_DOCK +IBM_PARAM(dock); +#endif +#ifdef CONFIG_ACPI_IBM_BAY +IBM_PARAM(bay); +#endif /* CONFIG_ACPI_IBM_BAY */ +IBM_PARAM(cmos); +IBM_PARAM(led); +IBM_PARAM(beep); +IBM_PARAM(ecdump); +IBM_PARAM(brightness); +IBM_PARAM(volume); +IBM_PARAM(fan); + +static int __init acpi_ibm_init(void) +{ + int ret, i; + + if (acpi_disabled) + return -ENODEV; + + /* ec is required because many other handles are relative to it */ + IBM_HANDLE_INIT(ec); + if (!ec_handle) { + printk(IBM_ERR "ec object not found\n"); + return -ENODEV; + } + + /* Models with newer firmware report the EC in DMI */ + ibm_thinkpad_ec_found = check_dmi_for_ec(); + + /* these handles are not required */ + IBM_HANDLE_INIT(vid); + IBM_HANDLE_INIT(vid2); + IBM_HANDLE_INIT(ledb); + IBM_HANDLE_INIT(led); + IBM_HANDLE_INIT(hkey); + IBM_HANDLE_INIT(lght); + IBM_HANDLE_INIT(cmos); +#ifdef CONFIG_ACPI_IBM_DOCK + IBM_HANDLE_INIT(dock); +#endif + IBM_HANDLE_INIT(pci); +#ifdef CONFIG_ACPI_IBM_BAY + IBM_HANDLE_INIT(bay); + if (bay_handle) + IBM_HANDLE_INIT(bay_ej); + IBM_HANDLE_INIT(bay2); + if (bay2_handle) + IBM_HANDLE_INIT(bay2_ej); +#endif /* CONFIG_ACPI_IBM_BAY */ + IBM_HANDLE_INIT(beep); + IBM_HANDLE_INIT(ecrd); + IBM_HANDLE_INIT(ecwr); + IBM_HANDLE_INIT(fans); + IBM_HANDLE_INIT(gfan); + IBM_HANDLE_INIT(sfan); + + proc_dir = proc_mkdir(IBM_DIR, acpi_root_dir); + if (!proc_dir) { + printk(IBM_ERR "unable to create proc dir %s", IBM_DIR); + acpi_ibm_exit(); + return -ENODEV; + } + proc_dir->owner = THIS_MODULE; + + for (i = 0; i < ARRAY_SIZE(ibms); i++) { + ret = ibm_init(&ibms[i]); + if (ret >= 0 && *ibms[i].param) + ret = ibms[i].write(ibms[i].param); + if (ret < 0) { + acpi_ibm_exit(); + return ret; + } + } + + return 0; +} + +static void acpi_ibm_exit(void) +{ + int i; + + for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--) + ibm_exit(&ibms[i]); + + if (proc_dir) + remove_proc_entry(IBM_DIR, acpi_root_dir); + + if (ibm_thinkpad_ec_found) + kfree(ibm_thinkpad_ec_found); +} + +module_init(acpi_ibm_init); +module_exit(acpi_ibm_exit); diff --git a/drivers/misc/ibm_acpi.h b/drivers/misc/ibm_acpi.h new file mode 100644 index 0000000..7ebaaa4 --- /dev/null +++ b/drivers/misc/ibm_acpi.h @@ -0,0 +1,437 @@ +/* + * ibm_acpi.h - IBM ThinkPad ACPI Extras + * + * + * Copyright (C) 2004-2005 Borislav Deianov + * Copyright (C) 2006-2007 Henrique de Moraes Holschuh + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __IBM_ACPI_H__ +#define __IBM_ACPI_H__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +/**************************************************************************** + * Main driver + */ + +#define IBM_NAME "ibm" +#define IBM_DESC "IBM ThinkPad ACPI Extras" +#define IBM_FILE "ibm_acpi" +#define IBM_URL "http://ibm-acpi.sf.net/" + +#define IBM_DIR IBM_NAME + +#define IBM_LOG IBM_FILE ": " +#define IBM_ERR KERN_ERR IBM_LOG +#define IBM_NOTICE KERN_NOTICE IBM_LOG +#define IBM_INFO KERN_INFO IBM_LOG +#define IBM_DEBUG KERN_DEBUG IBM_LOG + +#define IBM_MAX_ACPI_ARGS 3 + +/* ThinkPad CMOS commands */ +#define TP_CMOS_VOLUME_DOWN 0 +#define TP_CMOS_VOLUME_UP 1 +#define TP_CMOS_VOLUME_MUTE 2 +#define TP_CMOS_BRIGHTNESS_UP 4 +#define TP_CMOS_BRIGHTNESS_DOWN 5 + +#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off") +#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") +#define strlencmp(a,b) (strncmp((a), (b), strlen(b))) + +/* ACPI HIDs */ +#define IBM_HKEY_HID "IBM0068" +#define IBM_PCI_HID "PNP0A03" + +/* ACPI helpers */ +static int acpi_evalf(acpi_handle handle, + void *res, char *method, char *fmt, ...); +static int acpi_ec_read(int i, u8 * p); +static int acpi_ec_write(int i, u8 v); +static int _sta(acpi_handle handle); + +/* ACPI handles */ +static acpi_handle root_handle; /* root namespace */ +static acpi_handle ec_handle; /* EC */ +static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */ +static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */ + +static void ibm_handle_init(char *name, + acpi_handle * handle, acpi_handle parent, + char **paths, int num_paths, char **path); +#define IBM_HANDLE_INIT(object) \ + ibm_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + +/* procfs support */ +static struct proc_dir_entry *proc_dir; +static int ibm_acpi_driver_init(void); +static int ibm_acpi_driver_read(char *p); + +/* procfs helpers */ +static int dispatch_read(char *page, char **start, off_t off, int count, + int *eof, void *data); +static int dispatch_write(struct file *file, const char __user * userbuf, + unsigned long count, void *data); +static char *next_cmd(char **cmds); + +/* Module */ +static int experimental; +static char *ibm_thinkpad_ec_found; + +static char* check_dmi_for_ec(void); +static int acpi_ibm_init(void); +static void acpi_ibm_exit(void); + + +/**************************************************************************** + * Subdrivers + */ + +struct ibm_struct { + char *name; + char param[32]; + + char *hid; + struct acpi_driver *driver; + + int (*init) (void); + int (*read) (char *); + int (*write) (char *); + void (*exit) (void); + + void (*notify) (struct ibm_struct *, u32); + acpi_handle *handle; + int type; + struct acpi_device *device; + + int driver_registered; + int proc_created; + int init_called; + int notify_installed; + + int experimental; +}; + +static struct ibm_struct ibms[]; +static int set_ibm_param(const char *val, struct kernel_param *kp); +static int ibm_init(struct ibm_struct *ibm); +static void ibm_exit(struct ibm_struct *ibm); + +/* ACPI devices */ +static void dispatch_notify(acpi_handle handle, u32 event, void *data); +static int setup_notify(struct ibm_struct *ibm); +static int ibm_device_add(struct acpi_device *device); +static int register_ibmacpi_subdriver(struct ibm_struct *ibm); + + +/* + * Bay subdriver + */ + +#ifdef CONFIG_ACPI_IBM_BAY +static int bay_status_supported, bay_eject_supported; +static int bay_status2_supported, bay_eject2_supported; + +static acpi_handle bay_handle, bay_ej_handle; +static acpi_handle bay2_handle, bay2_ej_handle; + +static int bay_init(void); +static void bay_notify(struct ibm_struct *ibm, u32 event); +static int bay_read(char *p); +static int bay_write(char *buf); +#endif /* CONFIG_ACPI_IBM_BAY */ + + +/* + * Beep subdriver + */ + +static acpi_handle beep_handle; + +static int beep_read(char *p); +static int beep_write(char *buf); + + +/* + * Bluetooth subdriver + */ + +static int bluetooth_supported; + +static int bluetooth_init(void); +static int bluetooth_status(void); +static int bluetooth_read(char *p); +static int bluetooth_write(char *buf); + + +/* + * Brightness (backlight) subdriver + */ + +static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; + +static int brightness_init(void); +static void brightness_exit(void); +static int brightness_get(struct backlight_device *bd); +static int brightness_set(int value); +static int brightness_update_status(struct backlight_device *bd); +static int brightness_read(char *p); +static int brightness_write(char *buf); + + +/* + * CMOS subdriver + */ + +static int cmos_eval(int cmos_cmd); +static int cmos_read(char *p); +static int cmos_write(char *buf); + + +/* + * Dock subdriver + */ + +static acpi_handle pci_handle; +#ifdef CONFIG_ACPI_IBM_DOCK +static acpi_handle dock_handle; + +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); +#endif /* CONFIG_ACPI_IBM_DOCK */ + + +/* + * EC dump subdriver + */ + +static int ecdump_read(char *p) ; +static int ecdump_write(char *buf); + + +/* + * Fan subdriver + */ + +enum { /* Fan control constants */ + fan_status_offset = 0x2f, /* EC register 0x2f */ + fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM) + * 0x84 must be read before 0x85 */ + + IBMACPI_FAN_EC_DISENGAGED = 0x40, /* EC mode: tachometer + * disengaged */ + IBMACPI_FAN_EC_AUTO = 0x80, /* EC mode: auto fan + * control */ +}; + +enum fan_status_access_mode { + IBMACPI_FAN_NONE = 0, /* No fan status or control */ + IBMACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + IBMACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + IBMACPI_FAN_WR_NONE = 0, /* No fan control */ + IBMACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + IBMACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + IBMACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + IBMACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + IBMACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + IBMACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +static enum fan_status_access_mode fan_status_access_mode; +static enum fan_control_access_mode fan_control_access_mode; +static enum fan_control_commands fan_control_commands; +static int fan_control_status_known; +static u8 fan_control_initial_status; +static int fan_watchdog_maxinterval; + +static acpi_handle fans_handle, gfan_handle, sfan_handle; + +static int fan_init(void); +static void fan_exit(void); +static int fan_get_status(u8 *status); +static int fan_get_speed(unsigned int *speed); +static void fan_watchdog_fire(struct work_struct *ignored); +static void fan_watchdog_reset(void); +static int fan_set_level(int level); +static int fan_set_enable(void); +static int fan_set_disable(void); +static int fan_set_speed(int speed); +static int fan_read(char *p); +static int fan_write(char *buf); +static int fan_write_cmd_level(const char *cmd, int *rc); +static int fan_write_cmd_enable(const char *cmd, int *rc); +static int fan_write_cmd_disable(const char *cmd, int *rc); +static int fan_write_cmd_speed(const char *cmd, int *rc); +static int fan_write_cmd_watchdog(const char *cmd, int *rc); + + +/* + * Hotkey subdriver + */ + +static int hotkey_supported; +static int hotkey_mask_supported; +static int hotkey_orig_status; +static int hotkey_orig_mask; + +static int hotkey_init(void); +static void hotkey_exit(void); +static int hotkey_get(int *status, int *mask); +static int hotkey_set(int status, int mask); +static void hotkey_notify(struct ibm_struct *ibm, u32 event); +static int hotkey_read(char *p); +static int hotkey_write(char *buf); + + +/* + * LED subdriver + */ + +enum led_access_mode { + IBMACPI_LED_NONE = 0, + IBMACPI_LED_570, /* 570 */ + IBMACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + IBMACPI_LED_NEW, /* all others */ +}; + +enum { /* For IBMACPI_LED_OLD */ + IBMACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + IBMACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + IBMACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + +static enum led_access_mode led_supported; +static acpi_handle led_handle; + +static int led_init(void); +static int led_read(char *p); +static int led_write(char *buf); + +/* + * Light (thinklight) subdriver + */ + +static int light_supported; +static int light_status_supported; +static acpi_handle lght_handle, ledb_handle; + +static int light_init(void); +static int light_read(char *p); +static int light_write(char *buf); + + +/* + * Thermal subdriver + */ + +enum thermal_access_mode { + IBMACPI_THERMAL_NONE = 0, /* No thermal support */ + IBMACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + IBMACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + IBMACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + IBMACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[IBMACPI_MAX_THERMAL_SENSORS]; +}; + +static int thermal_init(void); +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); +static int thermal_read(char *p); + + +/* + * Video subdriver + */ + +enum video_access_mode { + IBMACPI_VIDEO_NONE = 0, + IBMACPI_VIDEO_570, /* 570 */ + IBMACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + IBMACPI_VIDEO_NEW, /* all others */ +}; + +static enum video_access_mode video_supported; +static int video_orig_autosw; +static acpi_handle vid_handle, vid2_handle; + +static int video_init(void); +static void video_exit(void); +static int video_status(void); +static int video_autosw(void); +static int video_switch(void); +static int video_switch2(int status); +static int video_expand(void); +static int video_read(char *p); +static int video_write(char *buf); + + +/* + * Volume subdriver + */ + +static int volume_offset = 0x30; + +static int volume_read(char *p); +static int volume_write(char *buf); + + +/* + * Wan subdriver + */ + +static int wan_supported; + +static int wan_init(void); +static int wan_status(void); +static int wan_read(char *p); +static int wan_write(char *buf); + + +#endif /* __IBM_ACPI_H */ diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 916c010..e375570 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -161,6 +161,7 @@ struct acpi_processor_flags { u8 bm_check:1; u8 has_cst:1; u8 power_setup_done:1; + u8 bm_rld_set:1; }; struct acpi_processor { @@ -275,6 +276,7 @@ int acpi_processor_power_init(struct acp int acpi_processor_cst_has_changed(struct acpi_processor *pr); int acpi_processor_power_exit(struct acpi_processor *pr, struct acpi_device *device); +extern struct cpuidle_driver acpi_idle_driver; /* in processor_thermal.c */ int acpi_processor_get_limit_info(struct acpi_processor *pr); diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h new file mode 100644 index 0000000..4a41f42 --- /dev/null +++ b/include/linux/cpuidle.h @@ -0,0 +1,183 @@ +/* + * cpuidle.h - a generic framework for CPU idle power management + * + * (C) 2007 Venkatesh Pallipadi + * Shaohua Li + * Adam Belay + * + * This code is licenced under the GPL. + */ + +#ifndef _LINUX_CPUIDLE_H +#define _LINUX_CPUIDLE_H + +#include +#include +#include +#include +#include + +#define CPUIDLE_STATE_MAX 8 +#define CPUIDLE_NAME_LEN 16 + +struct cpuidle_device; + + +/**************************** + * CPUIDLE DEVICE INTERFACE * + ****************************/ + +struct cpuidle_state { + char name[CPUIDLE_NAME_LEN]; + void *driver_data; + + unsigned int flags; + unsigned int exit_latency; /* in US */ + unsigned int power_usage; /* in mW */ + unsigned int target_residency; /* in US */ + + unsigned int usage; + unsigned int time; /* in US */ + + int (*enter) (struct cpuidle_device *dev, + struct cpuidle_state *state); + + struct kobject kobj; +}; + +/* Idle State Flags */ +#define CPUIDLE_FLAG_TIME_VALID (0x01) /* is residency time measurable? */ +#define CPUIDLE_FLAG_CHECK_BM (0x02) /* BM activity will exit state */ +#define CPUIDLE_FLAG_SHALLOW (0x10) /* low latency, minimal savings */ +#define CPUIDLE_FLAG_BALANCED (0x20) /* medium latency, moderate savings */ +#define CPUIDLE_FLAG_DEEP (0x40) /* high latency, large savings */ + +#define CPUIDLE_DRIVER_FLAGS_MASK (0xFFFF0000) + +/** + * cpuidle_get_statedata - retrieves private driver state data + * @state: the state + */ +static inline void * cpuidle_get_statedata(struct cpuidle_state *state) +{ + return state->driver_data; +} + +/** + * cpuidle_set_statedata - stores private driver state data + * @state: the state + * @data: the private data + */ +static inline void +cpuidle_set_statedata(struct cpuidle_state *state, void *data) +{ + state->driver_data = data; +} + +struct cpuidle_device { + unsigned int status; + int cpu; + + int last_residency; + int state_count; + struct cpuidle_state states[CPUIDLE_STATE_MAX]; + struct cpuidle_state *last_state; + + struct list_head device_list; + struct kobject kobj; + struct completion kobj_unregister; + void *governor_data; +}; + +#define to_cpuidle_device(n) container_of(n, struct cpuidle_device, kobj); + +DECLARE_PER_CPU(struct cpuidle_device, cpuidle_devices); + +/* Device Status Flags */ +#define CPUIDLE_STATUS_DETECTED (0x1) +#define CPUIDLE_STATUS_DRIVER_ATTACHED (0x2) +#define CPUIDLE_STATUS_GOVERNOR_ATTACHED (0x4) +#define CPUIDLE_STATUS_DOIDLE (CPUIDLE_STATUS_DETECTED | \ + CPUIDLE_STATUS_DRIVER_ATTACHED | \ + CPUIDLE_STATUS_GOVERNOR_ATTACHED) + +/** + * cpuidle_get_last_residency - retrieves the last state's residency time + * @dev: the target CPU + * + * NOTE: this value is invalid if CPUIDLE_FLAG_TIME_VALID isn't set + */ +static inline int cpuidle_get_last_residency(struct cpuidle_device *dev) +{ + return dev->last_residency; +} + + +/**************************** + * CPUIDLE DRIVER INTERFACE * + ****************************/ + +struct cpuidle_driver { + char name[CPUIDLE_NAME_LEN]; + struct list_head driver_list; + + int (*init) (struct cpuidle_device *dev); + void (*exit) (struct cpuidle_device *dev); + int (*redetect) (struct cpuidle_device *dev); + + int (*bm_check) (void); + + struct module *owner; +}; + +#ifdef CONFIG_CPU_IDLE + +extern int cpuidle_register_driver(struct cpuidle_driver *drv); +extern void cpuidle_unregister_driver(struct cpuidle_driver *drv); +extern int cpuidle_force_redetect(struct cpuidle_device *dev); + +#else + +static inline int cpuidle_register_driver(struct cpuidle_driver *drv) +{return 0;} +static inline void cpuidle_unregister_driver(struct cpuidle_driver *drv) { } +static inline int cpuidle_force_redetect(struct cpuidle_device *dev) +{return 0;} + +#endif + +/****************************** + * CPUIDLE GOVERNOR INTERFACE * + ******************************/ + +struct cpuidle_governor { + char name[CPUIDLE_NAME_LEN]; + struct list_head governor_list; + + int (*init) (struct cpuidle_device *dev); + void (*exit) (struct cpuidle_device *dev); + void (*scan) (struct cpuidle_device *dev); + + void (*prepare_idle) (struct cpuidle_device *dev); + int (*select_state) (struct cpuidle_device *dev); + + struct module *owner; +}; + +#ifdef CONFIG_CPU_IDLE + +extern int cpuidle_register_governor(struct cpuidle_governor *gov); +extern void cpuidle_unregister_governor(struct cpuidle_governor *gov); +extern int cpuidle_get_bm_activity(void); + +#else + +static inline int cpuidle_register_governor(struct cpuidle_governor *gov) +{return 0;} +static inline void cpuidle_unregister_governor(struct cpuidle_governor *gov) { } +static inline int cpuidle_get_bm_activity(void) +{return 0;} + +#endif + +#endif /* _LINUX_CPUIDLE_H */