GIT cc99216d4308ea0a19c61708fcdb59bee3f0e0a5 pm_test_attrgit+ssh://master.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6.git#test commit 17196d6e533a5c09ca57bf398099ffa3c13248b1 Author: Thomas Renninger Date: Fri Dec 7 13:20:43 2007 +0100 ACPI: Also autoload the bay driver, was forgotten... Signed-off-by: Thomas Renninger Signed-off-by: Len Brown commit a340af14b4c08a53c5f7d821d8bd910e17403384 Author: Frank Seidel Date: Fri Dec 7 13:20:42 2007 +0100 ACPI: Add autoload info to dock driver References: https://bugzilla.novell.com/show_bug.cgi?id=302482 Signed-off-by: Thomas Renninger Signed-off-by: Kay Sievers Signed-off-by: Frank Seidel Signed-off-by: Len Brown commit 3620f2f2f39e7870cf1a4fb2e34063a142f28716 Author: Frank Seidel Date: Fri Dec 7 13:20:34 2007 +0100 ACPI: Fix autloading of dock, video, bay and all linux specific HID drivers References: https://bugzilla.novell.com/show_bug.cgi?id=302482 Due to the new autloading of acpi drivers, the dock driver wasn't loaded anymore as there is no HID to identify it with (dock is needed if ACPI has a _DCK method). This patch is a workaround for this, original by Thomas Renninger, revised first by Kay Sievers and last by Frank Seidel. V2 of this patch fixed problems on systems without a defined _CID for the docking devices. Signed-off-by: Thomas Renninger Signed-off-by: Kay Sievers Signed-off-by: Frank Seidel Signed-off-by: Len Brown commit 77a97b6a9561f9df12ac90863a92005ddc5a34c5 Author: Johannes Berg Date: Sat Dec 8 02:14:00 2007 +0100 Suspend: Clean up Kconfig (V2) This cleans up the suspend Kconfig and removes the need to declare centrally which architectures support suspend. All architectures that currently support suspend are modified accordingly. Signed-off-by: Johannes Berg Acked-by: Russell King Acked-by: Paul Mackerras Acked-by: Ralf Baechle Acked-by: Paul Mundt Cc: Pavel Machek Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit 3c804cbdd6401f99ea8b012a18fec85db886e13f Author: Johannes Berg Date: Sat Dec 8 02:12:39 2007 +0100 Hibernation: Clean up Kconfig (V2) This cleans up the hibernation Kconfig and removes the need to declare centrally which architectures support hibernation. All architectures that currently support hibernation are modified accordingly. Signed-off-by: Johannes Berg Acked-by: Paul Mackerras Cc: Pavel Machek Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit f86f0b88abd8f98212a6a8f4b011652812776402 Author: Rafael J. Wysocki Date: Sat Dec 8 02:09:43 2007 +0100 Hibernation: Update messages Make hibernation messages start with one common prefix "PM: " and use the word "hibernation" in the messages as a synonym of "suspend to disk". Turn some KERN_INFO messages into debug ones. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit ce639cd48892012944de21bbe786d665cd903abd Author: Rafael J. Wysocki Date: Sat Dec 8 02:08:38 2007 +0100 Suspend: Use common prefix in messages Make suspend messages start with one common prefix "PM: ". Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit f68f977aec5babac733920e1c9d3b6986665ee19 Author: Rafael J. Wysocki Date: Sat Dec 8 02:07:40 2007 +0100 Hibernation: Remove unnecessary variable declaration Remove the unnecessary extern declaration of resume_file[] from kernel/power/swap.c . Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 45f02122763b4b77a266f0258e22f68789c8095c Author: Rafael J. Wysocki Date: Sat Dec 8 02:06:57 2007 +0100 Hibernation: Fix comment in disk.c Fix a comment in kernel/power/disk.c so that it doesn't contain lines longer that 80 characters. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 306ecc7e5e6cf7c34453ef99bd5937c18cd16701 Author: Rafael J. Wysocki Date: Sat Dec 8 02:06:00 2007 +0100 Suspend: Fix comment in main.c Fix a comment in kernel/power/main.c so that it doesn't contain lines longer that 80 characters. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 541dac0840c97cf38b53c528072a287bada76011 Author: Rafael J. Wysocki Date: Sat Dec 8 02:04:21 2007 +0100 Hibernation: Move low level resume to disk.c Move the low level restore code to kernel/power/disk.c , since the corresponding low level hibernation code is already there. Make restore fail if device_power_down(PMSG_PRETHAW) returns an error. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 14a5b234f554ffe8fd047671c8f7bd5b2996bd23 Author: Rafael J. Wysocki Date: Sat Dec 8 02:03:26 2007 +0100 Suspend: Fix compilation warning for CONFIG_SUSPEND unset Suspend: Make debug facility depend on CONFIG_SUSPEND Make the new suspend debug facility code depend on CONFIG_SUSPEND, as appropriate, to remove the compiler warning printed when CONFIG_PM is set and CONFIG_SUSPEND is not set. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 223630fe3dc564b94e51ff4eb839828c9083f2f6 Author: Zhang Rui Date: Thu Dec 6 23:36:35 2007 -0500 export thermal notification to userspace when nocrt is set module parameter is used to prevent the thermal_zone action upon critical trip points. But exporting this notification to userspace is still useful. By setting nocrt with this patch applied, ACPI will take no action but exporting the events to userspace upon critical/hot trip points. http://bugzilla.kernel.org/show_bug.cgi?id=9139 Signed-off-by: Zhang Rui Signed-off-by: Len Brown commit 669ab0b1fc35b815379c5d4171d99865f37abcd4 Author: Zhao Yakui Date: Fri Dec 7 11:18:35 2007 +0800 ACPI : Load device driver according to the status of acpi device According to ACPI spec when the status of some device is not present but functional, the device is valid and the children of this device should be enumerated. It means that the device should be added to linux acpi device tree. But the device driver for this device should not be loaded. http://bugzilla.kernel.org/show_bug.cgi?id=3358 Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Zhang Rui Signed-off-by: Len Brown commit ad40e68bf5c54831cd6b2199fd24d616bac179f7 Author: Andrey Borzenkov Date: Sat Nov 10 20:02:49 2007 +0300 ACPI: battery: fix ACPI battery technology reporting At least some systems report technology information with trailing spaces: {pts/1}% cat -E /var/tmp/bat/2.6.23 | grep type battery type: Li-ION $ Use strncasecmp to compare model string to skip trailing part Signed-off-by: Andrey Borzenkov Acked-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 17bc54eef91df29f0a22e8a1562a404cf7a68e74 Author: Alexey Starikovskiy Date: Tue Nov 13 13:05:45 2007 +0300 ACPI: Defer enabling of level GPE until all pending notifies done Level GPE should not be enabled until all work caused by it is done, e.g. all Notify() methods are completed. This can be accomplished by appending enable_gpe function to the end of notify queue. Signed-off-by: Alexey Starikovskiy Acked-by: Shaohua Li Signed-off-by: Len Brown commit 2c3512a220c2c7fe1c395dc7d7cced809f37c68d Author: Alan Stern Date: Mon Nov 19 23:49:18 2007 +0100 PM: Convert PM notifiers to out-of-line code This patch (as1008b) converts the PM notifier routines from inline calls to out-of-line code. It also prevents pm_chain_head from being created when CONFIG_PM_SLEEP isn't enabled, and EXPORTs the notifier registration and unregistration routines. Signed-off-by: Alan Stern Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit 3aa643dfe13037096fe4d603f2a095c5967a7edd Author: Johannes Berg Date: Mon Nov 19 23:46:16 2007 +0100 PM: Make PM_TRACE more architecture independent When trying to debug a suspend failure I started implementing PM_TRACE for powerpc. I then noticed that I'm debugging a suspend failure and so PM_TRACE isn't useful at all, but thought that nonetheless this could be useful in the future. Basically, to support PM_TRACE, you add a Kconfig option that selects PM_TRACE and provides the infrastructure as per the help text of PM_TRACE. Signed-off-by: Johannes Berg Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit e34e9533245660abde710b82f12a926ee5a23bdd Author: Rafael J. Wysocki Date: Mon Nov 19 23:43:34 2007 +0100 PM: Suspend/hibernation debug documentation update (rev. 2) Update the suspend/hibernation debugging and testing documentation to describe the newly introduced testing facilities. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit c4c190d079e74bfce9a4ed4d954c3fee5c2112b4 Author: Rafael J. Wysocki Date: Mon Nov 19 23:42:31 2007 +0100 Hibernation: New testing facility (rev. 2) Make it possible to test the hibernation core code with the help of the /sys/power/pm_test attribute introduced for suspend testing in the previous patch. Writing an appropriate string to this file causes the hibernation code to work in one of the test modes defined as follows: freezer - test the freezing of processes devices - test the freezing of processes and suspending of devices platform - test the freezing of processes, suspending of devices and platform global   control methods(*) processors - test the freezing of processes, suspending of devices, platform global   control methods(*) and the disabling of nonboot CPUs core - test the freezing of processes, suspending of devices, platform global   control methods(*), the disabling of nonboot CPUs and suspending of   platform/system devices (*) - the platform global control methods are only available on ACPI systems       and are only tested if the hibernation mode is set to "platform" Then, if a hibernation is started by normal means, the hibernation core will perform its normal operations up to the point indicated by given test level. Next, it will wait for 5 seconds and carry out the resume operations needed to transition the system back to the fully functional state. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit e2c86f9644b296c5b832bf847661eac7dae2f6af Author: Rafael J. Wysocki Date: Mon Nov 19 23:41:19 2007 +0100 Suspend: Testing facility (rev. 2) Introduce sysfs attribute /sys/power/pm_test allowing one to test the suspend core code.  Namely, writing one of the strings: freezer devices platform processors core to this file causes the suspend code to work in one of the test modes defined as follows: freezer - test the freezing of processes devices - test the freezing of processes and suspending of devices platform - test the freezing of processes, suspending of devices and platform global   control methods(*) processors - test the freezing of processes, suspending of devices, platform global   control methods and the disabling of nonboot CPUs core - test the freezing of processes, suspending of devices, platform global   control methods, the disabling of nonboot CPUs and suspending of   platform/system devices (*) These are ACPI global control methods on ACPI systems Then, if a suspend is started by normal means, the suspend core will perform its normal operations up to the point indicated by given test level.  Next, it will wait for 5 seconds and carry out the resume operations needed to transition the system back to the fully functional state. Writing "none" to /sys/power/pm_test turns the testing off. When open for reading, /sys/power/pm_test contains a space-separated list of all available tests (including "none" that represents the normal functionality) in which the current test level is indicated by square brackets. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit fc92adf7e89966848bea3515c8e84846682ac9d7 Author: Alan Stern Date: Mon Nov 19 23:38:25 2007 +0100 Hibernation: Add PM_RESTORE_PREPARE and PM_POST_RESTORE notifiers (rev. 2) Add PM_RESTORE_PREPARE and PM_POST_RESTORE notifiers to the PM core, to be used in analogy with the existing PM_HIBERNATION_PREPARE and PM_POST_HIBERNATION notifiers. Signed-off-by: Alan Stern Acked-by: Pavel Machek Signed-off-by: "Rafael J. Wysocki" Signed-off-by: Len Brown commit 930cd5ac8d5b7773da0b93e87d15ec4984fd3ab0 Author: Adrian Bunk Date: Mon Nov 19 23:36:20 2007 +0100 Hibernation: Move function prototypes to header This patch moves the prototypes of count_highmem_pages() and restore_highmem() to kernel/power/power.h Signed-off-by: Adrian Bunk Acked-by: Pavel Machek Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit 0ce7a6e578eda9e7d81b963adfb954bdad553a4a Author: Rafael J. Wysocki Date: Mon Nov 19 23:33:59 2007 +0100 ACPI: Fix mismerge in acpi_hibernation_finish Some code in acpi_hibernation_finish() was moved to acpi_hibernation_leave(), but the old copy had been left (it's harmless, but also useless). Remove it. Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown commit 88d905bda2b474473db1ff5de79c0213317e3a8a Author: Rafael J. Wysocki Date: Fri Oct 26 01:05:05 2007 +0200 Hibernation: Introduce exportable suspend ioctls header (rev. 2) Move the definitions of hibernation ioctls to a separate header file in include/linux, which can be exported to the user space. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit a9d69d078748178edaa81e6dc30a89f8877158ed Author: Rafael J. Wysocki Date: Fri Oct 26 01:03:33 2007 +0200 Hibernation: Correct definitions of some ioctls (rev. 2) Three ioctl numbers belonging to the hibernation userland interface, SNAPSHOT_ATOMIC_SNAPSHOT, SNAPSHOT_AVAIL_SWAP, SNAPSHOT_GET_SWAP_PAGE, are defined in a wrong way (eg. not portable). Provide new ioctl numbers for these ioctls and mark the existing ones as deprecated. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 1e7cbb536ca929f0c700c19f86494e7546f27c91 Author: Rafael J. Wysocki Date: Fri Oct 26 01:02:15 2007 +0200 Hibernation: Mark SNAPSHOT_SET_SWAP_FILE ioctl as deprecated (rev. 2) Mark the SNAPSHOT_SET_SWAP_FILE ioctl belonging to the hibernation userland interface as deprecated. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 56f86354798cc0354c2ec044cdb36528007e735d Author: Rafael J. Wysocki Date: Fri Oct 26 01:01:10 2007 +0200 Hibernation: Rework platform support ioctls (rev. 2) Modify the hibernation userland interface by adding two new ioctls to it, SNAPSHOT_PLATFORM_SUPPORT and SNAPSHOT_POWER_OFF, that can be used, respectively, to switch the hibernation platform support on/off and to make the kernel transition the system to the hibernation state (eg. ACPI S4) using the platform (eg. ACPI) driver. These ioctls are intended to replace the misdesigned SNAPSHOT_PMOPS ioctl, which from now is regarded as obsolete and will be removed in the future. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 7f083495ecbc8347b420a25b5bb0ac3cd1f38862 Author: Rafael J. Wysocki Date: Fri Oct 26 00:59:31 2007 +0200 Hibernation: Introduce SNAPSHOT_GET_IMAGE_SIZE ioctl Add a new ioctl, SNAPSHOT_GET_IMAGE_SIZE, returning the size of the (just created) hibernation image, to the hibernation userland interface. This ioctl is necessary so that the userland utilities using the interface need not access the hibernation image header, owned by the kernel, in order to obtain the size of the image. Signed-off-by: Rafael J. Wysocki Acked-by: Pavel Machek Signed-off-by: Len Brown commit 769224e5dc1549dc5efa2370c2b6aa2cc0d0e1ac Author: Len Brown Date: Fri Nov 23 20:08:02 2007 -0500 ACPI: tables: complete searching upon RSDP w/ bad checksum. ACPI tables follow a tree structure in memory. The root of the tree is the RSDP (Root System Description Pointer). To find the RSDP, the OS searches for the signature "RSD PTR " in well known physical memory locations. Then the OS computes a table checksum to verify that the signature is really part of a valid table header. Some systems have a proper signature but an invalid checksum; followed elsewhere by a proper signature with valid checksum. http://bugzilla.kernel.org/show_bug.cgi?id=9444 The Linux RSDP scanning code bailed out on those systems and as a result they booted with ACPI disabled. Fix this by deleting the Linux RSDP scanning code and plugging in the ACPICA RSDP scanning code. Signed-off-by: Len Brown commit 6ce7641b879e4b9ead46e14275d9d3645b47fa63 Author: Gary Hade Date: Tue Nov 20 12:19:12 2007 -0800 ACPI: acpiphp: Remove dmesg spam on device remove In cases where acpi_pci_bind() does not attach device data, acpi_pci_unbind() complains via an ACPI exception about the missing data when the device is removed. For example, acpi_pci_bind() does not attach data for non-existent device functions so when the device is removed using the ACPI PCI hotplug driver 'acpiphp' an ACPI exception is logged for every non-existent function. This patch avoids the confusing log messages by removing the unnecessary ACPI exception. Signed-off-by: Gary Hade Signed-off-by: Len Brown commit ed9cbcd40004904dbe61ccc16d6106a7de38c998 Author: Zhao Yakui Date: Tue Nov 20 14:20:21 2007 -0500 Revert "speedstep-lib.c: fix frequency multiplier for Pentium4 models 0&1" For P4 model < 2, The MSR_FBC_REGISTER_ID ratio is undefined. Revert the commit that was added to handle that case, as it results in random MHz displayed. Something else will have to be done to properly handle model < 2. //commit 3e4159ab35c88aef5e063ba78796b277b762a30a //Author: matthias.christian //Date: Sat Feb 5 23:09:38 2005 +0000 // // [PATCH] speedstep-lib.c: fix frequency multiplier for Pentium4 models 0&1 // // The Pentium4 models 0&1 have a longer MSR_EBC_FREQUENCY_ID register as the // models 2&3, so the bit shift must be bigger. // // Signed-off-by: Matthias-Christian Ott // Signed-off-by: Dominik Brodowski // Signed-off-by: Andrew Morton // Signed-off-by: Linus Torvalds // // BKrev: 42055232eWM-NgjhZVir44mp5GXktQ http://bugzilla.kernel.org/show_bug.cgi?id=7186 Signed-off-by: Zhao Yakui Signed-off-by: Len Brown commit a7f9b1f24974da287771e2d70b30d9ca7bd66684 Author: Zhang Rui Date: Tue Nov 20 13:38:59 2007 -0500 ACPI: disable stray GPE, prevent ACPI interrupt storm GPEs are disabled depending on their type -- WAKE, WAKE_RUN, and RUNTIME. An error is returned if we are asked to disable a GPE that has no type. But at least one system exists that enables a GPE from AML that is not the EC GPE, and has no _Lxx/_Exx AML handler, and is thus never initialized. In this case, when an external CRT is plugged in, the GPE fires, we attempt to disable the GPE, but instead just return an error. So the GPE stays asserted and an ACPI interrupt storm follows. The fix is to disable a firing GPE, even if it comes from outer space. http://bugzilla.kernel.org/show_bug.cgi?id=6217 Signed-off-by: Zhang Rui Acked-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 60555e371d56a1e410d9fb6fc68f4e953f8f4109 Author: len.brown@intel.com Date: Mon Nov 19 22:22:37 2007 -0500 ACPI: CONFIG_CPU_IDLE=ACPI by default In Linux-2.6.24, CPU_IDLE went upstream, default =n. For Linux-2.6.25, enable it by default on ACPI systems. For Linux-2.6.26, we plan to enable it always on ACPI systems. Signed-off-by: Len Brown commit b4d2730a0dda91a43c81a02f5225f5d536cabb09 Author: Len Brown Date: Wed Nov 14 19:53:21 2007 -0500 ACPI: document method tracing hooks Signed-off-by: Len Brown commit 4169c45f179e285feac6bcf25f4bd0db6b109bab Author: Zhang Rui Date: Wed Nov 14 19:38:40 2007 -0500 ACPI: add control method tracing support Add debug tracing support during certain AML method execution. Four more module parameters are created under /sys/module/acpi/parameters/: trace_method_name: the AML method name that user wants to trace trace_debug_layer: the temporary debug_layer used when tracing the method. Using 0xffffffff by default if it is 0. trace_debug_level: the temporary debug_level used when tracing the method. Using 0x00ffffff by default if it is 0. trace_state: The status of the tracing feature. "enabled" means this feature is enabled and the AML method is traced every time it's executed. "1" means this feature is enabled and the AML method will only be traced during the next execution. "disabled" means this feature is disabled. Users can enable/disable this debug tracing feature by "echo string > /sys/module/acpi/parameters/trace_state". "string" should be one of "enable", "disable" and "1". http://bugzilla.kernel.org/show_bug.cgi?id=6629 Signed-off-by: Zhang Rui Signed-off-by: Len Brown commit 43e8f9902dada1fbedd8404f8e946d37244e6008 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:37:02 2007 -0200 ACPI: thinkpad-acpi: wakeup on hotunplug reporting Handle some HKEY events that the firmware uses to report the reason for a wake up, and to also notify that the system could go back to sleep (if it woke up just to eject something from the bay, or to undock). The driver will report the reason of the last wake up in the sysfs attribute "wakeup_reason": 0 for "none, unknown, or standard ACPI wake up event", 1 for "bay ejection request" and 2 for "undock request". The firmware will also report if the operation that triggered the wake up has been completed, by issuing an HKEY 0x3003 or 0x4003 event. If the operation fails, no event is sent. When such a hotunplug sucessfull notification is issued, the driver sets the attribute "wakeup_hotunplug_complete" to 1. While the firmware does tell us whether we are waking from a suspend or hibernation scenario, the Linux way of hibernating makes this information not reliable, and therefore it is not reported. The idea is that if any of these attributes are non-zero, userspace might want to do something at the end of the "wake up from sleep" procedures, such as offering to send the machine back into sleep as soon as it is safe to do so. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 230c9a2290fab8c73e6b9203b7d2025a817556d6 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:37:01 2007 -0200 ACPI: thinkpad-acpi: cleanup hotkey_notify and HKEY log messages Use a generic message on hotkey_notify to log unknown and unhandled events, and cleanup hotkey_notify a little. Also, document event 0x5010 (brightness changed notification) and do not log it as an unknown event (even if we do not use it for anything right now). Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 9aa2c56549f3d6d347ca55a7dcde3fdf11a43f8d Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:37:00 2007 -0200 ACPI: thinkpad-acpi: add suspend handler Add a handler for suspend events. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 8d32c1894b6fb722c9136ced7a424ea69d1902f1 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:59 2007 -0200 ACPI: thinkpad-acpi: some checkpatch.pl fluff Fix some of the crap reported by checkpatch.pl. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 1e19330ba00ef7f8b712c08f51d2660868517682 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:58 2007 -0200 ACPI: thinkpad-acpi: rename IBM in defines Rename defines with IBM in their name that are related to the older driver name (ibm-acpi) to TPACPI, unless they are specific to IBM ThinkPads. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit b48f2792a854d080f7f2a9f0937a0a6e31e0c844 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:57 2007 -0200 ACPI: thinkpad-acpi: module glue cleanups General cleanup of module glue: Do some code reordering, and add missing parameter help text. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit f72c824c4caa202c6c156b57487586eae9aa68c0 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:56 2007 -0200 ACPI: thinkpad-acpi: spring cleanup part 4 Remove dead code, and anything in the old changelog that is not a thank you credit, or a key point to track down history. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 9cdcf0b0b9a7c791bed191f5aa9ad4487eb62b2e Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:55 2007 -0200 ACPI: thinkpad-acpi: spring cleanup part 3 Reorder code in the file to get rid of more of the forward declarations, and to make things cleaner and more organized. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit da63ac89cb1dfe6d6879028ca820dac23598846b Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:54 2007 -0200 ACPI: thinkpad-acpi: spring cleanup part 2 Move most subdriver-related stuff imported from the header file closer to their subdriver code. Also, delete unneeded forward declarations. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit ee5be340c34f0caac2a471672310217ab7b7965c Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:53 2007 -0200 ACPI: thinkpad-acpi: spring cleanup part 1 Remove the header file. Private header files used by a single .c file are in bad taste, and I know better now. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 57bdaebb2451859906f3acfea8af75ea94b6acb3 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:52 2007 -0200 ACPI: thinkpad-acpi: bump up version to 0.18 The NVRAM polling support for hot keys is reason enough to bump up the version string. Do it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 3ae590013fe59e4ff8151ce87c579fe3bea4e4eb Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:51 2007 -0200 ACPI: thinkpad-acpi: let threads be frozen Request that thinkpad-acpi threads be frozen during suspend or hibernate. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 0dcafdc062d32ba59967ac9404e6c076462a5a0f Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:50 2007 -0200 ACPI: thinkpad-acpi: add CMOS NVRAM polling for hot keys (v6) Older ThinkPad models do not export some of the hot keys over the event-based ACPI hot key interface. For these models, one has to poll the CMOS NVRAM to check the key state at a rate faster than the expected rate at which the user might repeatedly press the same hot key. This patch implements this functionality for many of the hotkeys in a transparent way: hot keys will now Just Work, and the driver knows the best approach (events or NVRAM polling) to employ, based on the HKEY.MHKA ACPI method. Also, the driver can turn off the polling when there are no users for the hot keys that need such polling. The NVRAM-based hot keys of the A3x series that have never been implemented by later models are not supported, to avoid changes in the keymap of the input devices that could cause headaches in the future. There is a Kconfig option to avoid compiling the NVRAM polling code, as it is not very small, and unlikely to be useful on any ThinkPad newer than a T40, X31 or R52. This patch is based on a previous effort by Richard Hughes. Signed-off-by: Henrique de Moraes Holschuh Cc: Richard Hughes Signed-off-by: Len Brown commit ceafedf0cbb1fb7b0a06dad0dbcc9e2ed4189b96 Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:49 2007 -0200 ACPI: thinkpad-acpi: prepare for NVRAM polling support Make some small internal thinkpad-acpi changes to the hotkey subdriver code that will make it easier to add NVRAM polling support. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit d5889d8c8d6091fa9fe9e0b77c49116ff557366a Author: Henrique de Moraes Holschuh Date: Thu Nov 15 01:36:48 2007 -0200 ACPI: thinkpad-acpi: refactor hotkey_get and hotkey_set This patch: 1. Splits hotkey_get/set into hotkey_status_get/set and hotkey_mask_get/set; 2. Caches the status of hot key mask for later driver use; 3. Makes sure the cache of hot key mask is refreshed when needed; 4. logs a printk notice when the firmware doesn't set the hot key mask to exactly what we asked it to; 5. Do the proper locking on the data structures. Only (4) is user-noticeable, unless (5) fixes some corner-case races. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 089ef846d49c8bf77ed2100d5b000fad0febb2f5 Author: Dmitry Torokhov Date: Mon Nov 5 11:43:36 2007 -0500 ACPI: video - fix permissions on some proc entries POST and DOS are supposed to be writable but permissions did not allow it. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit 4e905071b97fd41ad4f328bcd919c37625230bbe Author: Dmitry Torokhov Date: Mon Nov 5 11:43:35 2007 -0500 ACPI: video - more cleanups Remove unneeded checks and initializations, implement proper unwinding after errors in initialization code, get rid of unneeded casts, adjust formatting. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit 162175dedc1b3331219395aa391bf6abb743a86d Author: Dmitry Torokhov Date: Mon Nov 5 11:43:34 2007 -0500 ACPI: video - properly handle errors when registering proc elements Have acpi_video_device_add_fs() and acpi_video_bus_add_fs() properly unwind proc creation after error. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit 3e79c0e68cd0297ada8442860857ec1f8b701bbf Author: Dmitry Torokhov Date: Mon Nov 5 11:43:33 2007 -0500 ACPI: video - do not store invalid entries in attached_array list this is a cleanup, not a change to function. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown Documentation/00-INDEX | 3 + Documentation/acpi/method-tracing.txt | 26 + Documentation/power/basic-pm-debugging.txt | 216 ++- Documentation/power/drivers-testing.txt | 30 +- Documentation/power/notifiers.txt | 8 + Documentation/power/userland-swsusp.txt | 82 +- Documentation/thinkpad-acpi.txt | 92 +- arch/arm/Kconfig | 3 + arch/blackfin/Kconfig | 4 + arch/frv/Kconfig | 5 + arch/mips/Kconfig | 4 + arch/powerpc/Kconfig | 18 +- arch/sh/Kconfig | 4 + arch/x86/Kconfig | 6 + arch/x86/kernel/acpi/boot.c | 40 - arch/x86/kernel/cpu/cpufreq/speedstep-lib.c | 5 +- arch/x86/kernel/srat_32.c | 2 +- drivers/acpi/battery.c | 2 +- drivers/acpi/bay.c | 6 + drivers/acpi/bus.c | 11 +- drivers/acpi/debug.c | 57 + drivers/acpi/dock.c | 6 + drivers/acpi/events/evgpe.c | 25 +- drivers/acpi/osl.c | 50 +- drivers/acpi/pci_bind.c | 3 - drivers/acpi/scan.c | 118 +- drivers/acpi/sleep/main.c | 5 - drivers/acpi/tables/Makefile | 2 +- drivers/acpi/tables/tbxfroot.c | 4 +- drivers/acpi/thermal.c | 17 +- drivers/acpi/video.c | 1053 +++++----- drivers/base/power/Makefile | 2 +- drivers/cpuidle/Kconfig | 4 +- drivers/misc/Kconfig | 19 + drivers/misc/thinkpad_acpi.c | 3098 ++++++++++++++++++--------- drivers/misc/thinkpad_acpi.h | 606 ------ drivers/pnp/pnpacpi/core.c | 6 +- include/acpi/acpi_drivers.h | 1 + include/linux/Kbuild | 1 + include/linux/acpi.h | 1 - include/linux/notifier.h | 2 + include/linux/suspend.h | 13 +- include/linux/suspend_ioctls.h | 32 + kernel/power/Kconfig | 60 +- kernel/power/disk.c | 154 ++- kernel/power/main.c | 146 ++- kernel/power/power.h | 72 +- kernel/power/snapshot.c | 31 +- kernel/power/swap.c | 33 +- kernel/power/swsusp.c | 48 +- kernel/power/user.c | 109 +- 51 files changed, 3642 insertions(+), 2703 deletions(-) diff --git a/Documentation/00-INDEX b/Documentation/00-INDEX index c3014df..0c6996e 100644 --- a/Documentation/00-INDEX +++ b/Documentation/00-INDEX @@ -14,6 +14,7 @@ Following translations are available on the WWW: - this file. ABI/ - info on kernel <-> userspace ABI and relative interface stability. + BUG-HUNTING - brute force method of doing binary search of patches to find bug. Changes @@ -66,6 +67,8 @@ VGA-softcursor.txt - how to change your VGA cursor from a blinking underscore. accounting/ - documentation on accounting and taskstats. +acpi/ + - info on ACPI-specific hooks in the kernel. aoe/ - description of AoE (ATA over Ethernet) along with config examples. applying-patches.txt diff --git a/Documentation/acpi/method-tracing.txt b/Documentation/acpi/method-tracing.txt new file mode 100644 index 0000000..f6efb1e --- /dev/null +++ b/Documentation/acpi/method-tracing.txt @@ -0,0 +1,26 @@ +/sys/module/acpi/parameters/: + +trace_method_name + The AML method name that the user wants to trace + +trace_debug_layer + The temporary debug_layer used when tracing the method. + Using 0xffffffff by default if it is 0. + +trace_debug_level + The temporary debug_level used when tracing the method. + Using 0x00ffffff by default if it is 0. + +trace_state + The status of the tracing feature. + + "enabled" means this feature is enabled + and the AML method is traced every time it's executed. + + "1" means this feature is enabled and the AML method + will only be traced during the next execution. + + "disabled" means this feature is disabled. + Users can enable/disable this debug tracing feature by + "echo string > /sys/module/acpi/parameters/trace_state". + "string" should be one of "enable", "disable" and "1". diff --git a/Documentation/power/basic-pm-debugging.txt b/Documentation/power/basic-pm-debugging.txt index 57aef2f..1555001 100644 --- a/Documentation/power/basic-pm-debugging.txt +++ b/Documentation/power/basic-pm-debugging.txt @@ -1,45 +1,111 @@ -Debugging suspend and resume +Debugging hibernation and suspend (C) 2007 Rafael J. Wysocki , GPL -1. Testing suspend to disk (STD) +1. Testing hibernation (aka suspend to disk or STD) -To verify that the STD works, you can try to suspend in the "reboot" mode: +To check if hibernation works, you can try to hibernate in the "reboot" mode: # echo reboot > /sys/power/disk # echo disk > /sys/power/state -and the system should suspend, reboot, resume and get back to the command prompt -where you have started the transition. If that happens, the STD is most likely -to work correctly, but you need to repeat the test at least a couple of times in -a row for confidence. This is necessary, because some problems only show up on -a second attempt at suspending and resuming the system. You should also test -the "platform" and "shutdown" modes of suspend: +and the system should create a hibernation image, reboot, resume and get back to +the command prompt where you have started the transition. If that happens, +hibernation is most likely to work correctly. Still, you need to repeat the +test at least a couple of times in a row for confidence. [This is necessary, +because some problems only show up on a second attempt at suspending and +resuming the system.] Moreover, hibernating in the "reboot" and "shutdown" +modes causes the PM core to skip some platform-related callbacks which on ACPI +systems might be necessary to make hibernation work. Thus, if you machine fails +to hibernate or resume in the "reboot" mode, you should try the "platform" mode: # echo platform > /sys/power/disk # echo disk > /sys/power/state -or +which is the default and recommended mode of hibernation. + +Unfortunately, the "platform" mode of hibernation does not work on some systems +with broken BIOSes. In such cases the "shutdown" mode of hibernation might +work: # echo shutdown > /sys/power/disk # echo disk > /sys/power/state -in which cases you will have to press the power button to make the system -resume. If that does not work, you will need to identify what goes wrong. +(it is similar to the "reboot" mode, but it requires you to press the power +button to make the system resume). + +If neither "platform" nor "shutdown" hibernation mode works, you will need to +identify what goes wrong. + +a) Test modes of hibernation + +To find out why hibernation fails on your system, you can use a special testing +facility available if the kernel is compiled with CONFIG_PM_DEBUG set. Then, +there is the file /sys/power/pm_test that can be used to make the hibernation +core run in a test mode. There are 5 test modes available: + +freezer +- test the freezing of processes + +devices +- test the freezing of processes and suspending of devices -a) Test mode of STD +platform +- test the freezing of processes, suspending of devices and platform + global control methods(*) -To verify if there are any drivers that cause problems you can run the STD -in the test mode: +processors +- test the freezing of processes, suspending of devices, platform + global control methods(*) and the disabling of nonboot CPUs -# echo test > /sys/power/disk +core +- test the freezing of processes, suspending of devices, platform global + control methods(*), the disabling of nonboot CPUs and suspending of + platform/system devices + +(*) the platform global control methods are only available on ACPI systems + and are only tested if the hibernation mode is set to "platform" + +To use one of them it is necessary to write the corresponding string to +/sys/power/pm_test (eg. "devices" to test the freezing of processes and +suspending devices) and issue the standard hibernation commands. For example, +to use the "devices" test mode along with the "platform" mode of hibernation, +you should do the following: + +# echo devices > /sys/power/pm_test +# echo platform > /sys/power/disk # echo disk > /sys/power/state -in which case the system should freeze tasks, suspend devices, disable nonboot -CPUs (if any), wait for 5 seconds, enable nonboot CPUs, resume devices, thaw -tasks and return to your command prompt. If that fails, most likely there is -a driver that fails to either suspend or resume (in the latter case the system -may hang or be unstable after the test, so please take that into consideration). -To find this driver, you can carry out a binary search according to the rules: +Then, the kernel will try to freeze processes, suspend devices, wait 5 seconds, +resume devices and thaw processes. If "platform" is written to +/sys/power/pm_test , then after suspending devices the kernel will additionally +invoke the global control methods (eg. ACPI global control methods) used to +prepare the platform firmware for hibernation. Next, it will wait 5 seconds and +invoke the platform (eg. ACPI) global methods used to cancel hibernation etc. + +Writing "none" to /sys/power/pm_test causes the kernel to switch to the normal +hibernation/suspend operations. Also, when open for reading, /sys/power/pm_test +contains a space-separated list of all available tests (including "none" that +represents the normal functionality) in which the current test level is +indicated by square brackets. + +Generally, as you can see, each test level is more "invasive" than the previous +one and the "core" level tests the hardware and drivers as deeply as possible +without creating a hibernation image. Obviously, if the "devices" test fails, +the "platform" test will fail as well and so on. Thus, as a rule of thumb, you +should try the test modes starting from "freezer", through "devices", "platform" +and "processors" up to "core" (repeat the test on each level a couple of times +to make sure that any random factors are avoided). + +If the "freezer" test fails, there is a task that cannot be frozen (in that case +it usually is possible to identify the offending task by analysing the output of +dmesg obtained after the failing test). Failure at this level usually means +that there is a problem with the tasks freezer subsystem that should be +reported. + +If the "devices" test fails, most likely there is a driver that cannot suspend +or resume its device (in the latter case the system may hang or become unstable +after the test, so please take that into consideration). To find this driver, +you can carry out a binary search according to the rules: - if the test fails, unload a half of the drivers currently loaded and repeat (that would probably involve rebooting the system, so always note what drivers have been loaded before the test), @@ -47,23 +113,46 @@ have been loaded before the test), recently and repeat. Once you have found the failing driver (there can be more than just one of -them), you have to unload it every time before the STD transition. In that case -please make sure to report the problem with the driver. - -It is also possible that a cycle can still fail after you have unloaded -all modules. In that case, you would want to look in your kernel configuration -for the drivers that can be compiled as modules (testing again with them as -modules), and possibly also try boot time options such as "noapic" or "noacpi". +them), you have to unload it every time before hibernation. In that case please +make sure to report the problem with the driver. + +It is also possible that the "devices" test will still fail after you have +unloaded all modules. In that case, you may want to look in your kernel +configuration for the drivers that can be compiled as modules (and test again +with these drivers compiled as modules). You may also try to use some special +kernel command line options such as "noapic", "noacpi" or even "acpi=off". + +If the "platform" test fails, there is a problem with the handling of the +platform (eg. ACPI) firmware on your system. In that case the "platform" mode +of hibernation is not likely to work. You can try the "shutdown" mode, but that +is rather a poor man's workaround. + +If the "processors" test fails, the disabling/enabling of nonboot CPUs does not +work (of course, this only may be an issue on SMP systems) and the problem +should be reported. In that case you can also try to switch the nonboot CPUs +off and on using the /sys/devices/system/cpu/cpu*/online sysfs attributes and +see if that works. + +If the "core" test fails, which means that suspending of the system/platform +devices has failed (these devices are suspended on one CPU with interrupts off), +the problem is most probably hardware-related and serious, so it should be +reported. + +A failure of any of the "platform", "processors" or "core" tests may cause your +system to hang or become unstable, so please beware. Such a failure usually +indicates a serious problem that very well may be related to the hardware, but +please report it anyway. b) Testing minimal configuration -If the test mode of STD works, you can boot the system with "init=/bin/bash" -and attempt to suspend in the "reboot", "shutdown" and "platform" modes. If -that does not work, there probably is a problem with a driver statically -compiled into the kernel and you can try to compile more drivers as modules, -so that they can be tested individually. Otherwise, there is a problem with a -modular driver and you can find it by loading a half of the modules you normally -use and binary searching in accordance with the algorithm: +If all of the hibernation test modes work, you can boot the system with the +"init=/bin/bash" command line parameter and attempt to hibernate in the +"reboot", "shutdown" and "platform" modes. If that does not work, there +probably is a problem with a driver statically compiled into the kernel and you +can try to compile more drivers as modules, so that they can be tested +individually. Otherwise, there is a problem with a modular driver and you can +find it by loading a half of the modules you normally use and binary searching +in accordance with the algorithm: - if there are n modules loaded and the attempt to suspend and resume fails, unload n/2 of the modules and try again (that would probably involve rebooting the system), @@ -71,19 +160,19 @@ the system), load n/2 modules more and try again. Again, if you find the offending module(s), it(they) must be unloaded every time -before the STD transition, and please report the problem with it(them). +before hibernation, and please report the problem with it(them). c) Advanced debugging -In case the STD does not work on your system even in the minimal configuration -and compiling more drivers as modules is not practical or some modules cannot -be unloaded, you can use one of the more advanced debugging techniques to find -the problem. First, if there is a serial port in your box, you can boot the -kernel with the 'no_console_suspend' parameter and try to log kernel -messages using the serial console. This may provide you with some information -about the reasons of the suspend (resume) failure. Alternatively, it may be -possible to use a FireWire port for debugging with firescope -(ftp://ftp.firstfloor.org/pub/ak/firescope/). On i386 it is also possible to +In case that hibernation does not work on your system even in the minimal +configuration and compiling more drivers as modules is not practical or some +modules cannot be unloaded, you can use one of the more advanced debugging +techniques to find the problem. First, if there is a serial port in your box, +you can boot the kernel with the 'no_console_suspend' parameter and try to log +kernel messages using the serial console. This may provide you with some +information about the reasons of the suspend (resume) failure. Alternatively, +it may be possible to use a FireWire port for debugging with firescope +(ftp://ftp.firstfloor.org/pub/ak/firescope/). On x86 it is also possible to use the PM_TRACE mechanism documented in Documentation/s2ram.txt . 2. Testing suspend to RAM (STR) @@ -91,16 +180,25 @@ use the PM_TRACE mechanism documented in Documentation/s2ram.txt . To verify that the STR works, it is generally more convenient to use the s2ram tool available from http://suspend.sf.net and documented at http://en.opensuse.org/s2ram . However, before doing that it is recommended to -carry out the procedure described in section 1. - -Assume you have resolved the problems with the STD and you have found some -failing drivers. These drivers are also likely to fail during the STR or -during the resume, so it is better to unload them every time before the STR -transition. Now, you can follow the instructions at -http://en.opensuse.org/s2ram to test the system, but if it does not work -"out of the box", you may need to boot it with "init=/bin/bash" and test -s2ram in the minimal configuration. In that case, you may be able to search -for failing drivers by following the procedure analogous to the one described in -1b). If you find some failing drivers, you will have to unload them every time -before the STR transition (ie. before you run s2ram), and please report the -problems with them. +carry out STR testing using the facility described in section 1. + +Namely, after writing "freezer", "devices", "platform", "processors", or "core" +into /sys/power/pm_test (available if the kernel is compiled with +CONFIG_PM_DEBUG set) the suspend code will work in the test mode corresponding +to given string. The STR test modes are defined in the same way as for +hibernation, so please refer to Section 1 for more information about them. In +particular, the "core" test allows you to test everything except for the actual +invocation of the platform firmware in order to put the system into the sleep +state. + +Among other things, the testing with the help of /sys/power/pm_test may allow +you to identify drivers that fail to suspend or resume their devices. They +should be unloaded every time before an STR transition. + +Next, you can follow the instructions at http://en.opensuse.org/s2ram to test +the system, but if it does not work "out of the box", you may need to boot it +with "init=/bin/bash" and test s2ram in the minimal configuration. In that +case, you may be able to search for failing drivers by following the procedure +analogous to the one described in section 1. If you find some failing drivers, +you will have to unload them every time before an STR transition (ie. before +you run s2ram), and please report the problems with them. diff --git a/Documentation/power/drivers-testing.txt b/Documentation/power/drivers-testing.txt index e4bdcae..7f7a737 100644 --- a/Documentation/power/drivers-testing.txt +++ b/Documentation/power/drivers-testing.txt @@ -6,9 +6,9 @@ Testing suspend and resume support in device drivers Unfortunately, to effectively test the support for the system-wide suspend and resume transitions in a driver, it is necessary to suspend and resume a fully functional system with this driver loaded. Moreover, that should be done -several times, preferably several times in a row, and separately for the suspend -to disk (STD) and the suspend to RAM (STR) transitions, because each of these -cases involves different ordering of operations and different interactions with +several times, preferably several times in a row, and separately for hibernation +(aka suspend to disk or STD) and suspend to RAM (STR), because each of these +cases involves slightly different operations and different interactions with the machine's BIOS. Of course, for this purpose the test system has to be known to suspend and @@ -22,20 +22,24 @@ for more information about the debugging of suspend/resume functionality. Once you have resolved the suspend/resume-related problems with your test system without the new driver, you are ready to test it: -a) Build the driver as a module, load it and try the STD in the test mode (see: -Documents/power/basic-pm-debugging.txt, 1a)). +a) Build the driver as a module, load it and try the test modes of hibernation + (see: Documents/power/basic-pm-debugging.txt, 1). -b) Load the driver and attempt to suspend to disk in the "reboot", "shutdown" -and "platform" modes (see: Documents/power/basic-pm-debugging.txt, 1). +b) Load the driver and attempt to hibernate in the "reboot", "shutdown" and + "platform" modes (see: Documents/power/basic-pm-debugging.txt, 1). -c) Compile the driver directly into the kernel and try the STD in the test mode. +c) Compile the driver directly into the kernel and try the test modes of + hibernation. -d) Attempt to suspend to disk with the driver compiled directly into the kernel -in the "reboot", "shutdown" and "platform" modes. +d) Attempt to hibernate with the driver compiled directly into the kernel + in the "reboot", "shutdown" and "platform" modes. -e) Attempt to suspend to RAM using the s2ram tool with the driver loaded (see: -Documents/power/basic-pm-debugging.txt, 2). As far as the STR tests are -concerned, it should not matter whether or not the driver is built as a module. +e) Try the test modes of suspend (see: Documents/power/basic-pm-debugging.txt, + 2). [As far as the STR tests are concerned, it should not matter whether or + not the driver is built as a module.] + +f) Attempt to suspend to RAM using the s2ram tool with the driver loaded + (see: Documents/power/basic-pm-debugging.txt, 2). Each of the above tests should be repeated several times and the STD tests should be mixed with the STR tests. If any of them fails, the driver cannot be diff --git a/Documentation/power/notifiers.txt b/Documentation/power/notifiers.txt index 9293e4b..ae1b7ec 100644 --- a/Documentation/power/notifiers.txt +++ b/Documentation/power/notifiers.txt @@ -28,6 +28,14 @@ PM_POST_HIBERNATION The system memory state has been restored from a hibernation. Device drivers' .resume() callbacks have been executed and tasks have been thawed. +PM_RESTORE_PREPARE The system is going to restore a hibernation image. + If all goes well the restored kernel will issue a + PM_POST_HIBERNATION notification. + +PM_POST_RESTORE An error occurred during the hibernation restore. + Device drivers' .resume() callbacks have been executed + and tasks have been thawed. + PM_SUSPEND_PREPARE The system is preparing for a suspend. PM_POST_SUSPEND The system has just resumed or an error occured during diff --git a/Documentation/power/userland-swsusp.txt b/Documentation/power/userland-swsusp.txt index e00c6cf..7b99636 100644 --- a/Documentation/power/userland-swsusp.txt +++ b/Documentation/power/userland-swsusp.txt @@ -14,7 +14,7 @@ are going to develop your own suspend/resume utilities. The interface consists of a character device providing the open(), release(), read(), and write() operations as well as several ioctl() -commands defined in kernel/power/power.h. The major and minor +commands defined in include/linux/suspend_ioctls.h . The major and minor numbers of the device are, respectively, 10 and 231, and they can be read from /sys/class/misc/snapshot/dev. @@ -27,17 +27,17 @@ once at a time. The ioctl() commands recognized by the device are: SNAPSHOT_FREEZE - freeze user space processes (the current process is - not frozen); this is required for SNAPSHOT_ATOMIC_SNAPSHOT + not frozen); this is required for SNAPSHOT_CREATE_IMAGE and SNAPSHOT_ATOMIC_RESTORE to succeed SNAPSHOT_UNFREEZE - thaw user space processes frozen by SNAPSHOT_FREEZE -SNAPSHOT_ATOMIC_SNAPSHOT - create a snapshot of the system memory; the +SNAPSHOT_CREATE_IMAGE - create a snapshot of the system memory; the last argument of ioctl() should be a pointer to an int variable, the value of which will indicate whether the call returned after creating the snapshot (1) or after restoring the system memory state from it (0) (after resume the system finds itself finishing the - SNAPSHOT_ATOMIC_SNAPSHOT ioctl() again); after the snapshot + SNAPSHOT_CREATE_IMAGE ioctl() again); after the snapshot has been created the read() operation can be used to transfer it out of the kernel @@ -49,39 +49,37 @@ SNAPSHOT_ATOMIC_RESTORE - restore the system memory state from the SNAPSHOT_FREE - free memory allocated for the snapshot image -SNAPSHOT_SET_IMAGE_SIZE - set the preferred maximum size of the image +SNAPSHOT_PREF_IMAGE_SIZE - set the preferred maximum size of the image (the kernel will do its best to ensure the image size will not exceed this number, but if it turns out to be impossible, the kernel will create the smallest image possible) -SNAPSHOT_AVAIL_SWAP - return the amount of available swap in bytes (the last - argument should be a pointer to an unsigned int variable that will +SNAPSHOT_GET_IMAGE_SIZE - return the actual size of the hibernation image + +SNAPSHOT_AVAIL_SWAP_SIZE - return the amount of available swap in bytes (the + last argument should be a pointer to an unsigned int variable that will contain the result if the call is successful). -SNAPSHOT_GET_SWAP_PAGE - allocate a swap page from the resume partition +SNAPSHOT_ALLOC_SWAP_PAGE - allocate a swap page from the resume partition (the last argument should be a pointer to a loff_t variable that will contain the swap page offset if the call is successful) -SNAPSHOT_FREE_SWAP_PAGES - free all swap pages allocated with - SNAPSHOT_GET_SWAP_PAGE - -SNAPSHOT_SET_SWAP_FILE - set the resume partition (the last ioctl() argument - should specify the device's major and minor numbers in the old - two-byte format, as returned by the stat() function in the .st_rdev - member of the stat structure) +SNAPSHOT_FREE_SWAP_PAGES - free all swap pages allocated by + SNAPSHOT_ALLOC_SWAP_PAGE SNAPSHOT_SET_SWAP_AREA - set the resume partition and the offset (in units) from the beginning of the partition at which the swap header is located (the last ioctl() argument should point to a struct - resume_swap_area, as defined in kernel/power/power.h, containing the - resume device specification, as for the SNAPSHOT_SET_SWAP_FILE ioctl(), - and the offset); for swap partitions the offset is always 0, but it is - different to zero for swap files (please see - Documentation/swsusp-and-swap-files.txt for details). - The SNAPSHOT_SET_SWAP_AREA ioctl() is considered as a replacement for - SNAPSHOT_SET_SWAP_FILE which is regarded as obsolete. It is - recommended to always use this call, because the code to set the resume - partition may be removed from future kernels + resume_swap_area, as defined in kernel/power/suspend_ioctls.h, + containing the resume device specification and the offset); for swap + partitions the offset is always 0, but it is different from zero for + swap files (see Documentation/swsusp-and-swap-files.txt for details). + +SNAPSHOT_PLATFORM_SUPPORT - enable/disable the hibernation platform support, + depending on the argument value (enable, if the argument is nonzero) + +SNAPSHOT_POWER_OFF - make the kernel transition the system to the hibernation + state (eg. ACPI S4) using the platform (eg. ACPI) driver SNAPSHOT_S2RAM - suspend to RAM; using this call causes the kernel to immediately enter the suspend-to-RAM state, so this call must always @@ -93,24 +91,6 @@ SNAPSHOT_S2RAM - suspend to RAM; using this call causes the kernel to to resume the system from RAM if there's enough battery power or restore its state on the basis of the saved suspend image otherwise) -SNAPSHOT_PMOPS - enable the usage of the hibernation_ops->prepare, - hibernate_ops->enter and hibernation_ops->finish methods (the in-kernel - swsusp knows these as the "platform method") which are needed on many - machines to (among others) speed up the resume by letting the BIOS skip - some steps or to let the system recognise the correct state of the - hardware after the resume (in particular on many machines this ensures - that unplugged AC adapters get correctly detected and that kacpid does - not run wild after the resume). The last ioctl() argument can take one - of the three values, defined in kernel/power/power.h: - PMOPS_PREPARE - make the kernel carry out the - hibernation_ops->prepare() operation - PMOPS_ENTER - make the kernel power off the system by calling - hibernation_ops->enter() - PMOPS_FINISH - make the kernel carry out the - hibernation_ops->finish() operation - Note that the actual constants are misnamed because they surface - internal kernel implementation details that have changed. - The device's read() operation can be used to transfer the snapshot image from the kernel. It has the following limitations: - you cannot read() more than one virtual memory page at a time @@ -122,7 +102,7 @@ The device's write() operation is used for uploading the system memory snapshot into the kernel. It has the same limitations as the read() operation. The release() operation frees all memory allocated for the snapshot image -and all swap pages allocated with SNAPSHOT_GET_SWAP_PAGE (if any). +and all swap pages allocated with SNAPSHOT_ALLOC_SWAP_PAGE (if any). Thus it is not necessary to use either SNAPSHOT_FREE or SNAPSHOT_FREE_SWAP_PAGES before closing the device (in fact it will also unfreeze user space processes frozen by SNAPSHOT_UNFREEZE if they are @@ -133,16 +113,12 @@ snapshot image from/to the kernel will use a swap parition, called the resume partition, or a swap file as storage space (if a swap file is used, the resume partition is the partition that holds this file). However, this is not really required, as they can use, for example, a special (blank) suspend partition or -a file on a partition that is unmounted before SNAPSHOT_ATOMIC_SNAPSHOT and +a file on a partition that is unmounted before SNAPSHOT_CREATE_IMAGE and mounted afterwards. -These utilities SHOULD NOT make any assumptions regarding the ordering of -data within the snapshot image, except for the image header that MAY be -assumed to start with an swsusp_info structure, as specified in -kernel/power/power.h. This structure MAY be used by the userland utilities -to obtain some information about the snapshot image, such as the size -of the snapshot image, including the metadata and the header itself, -contained in the .size member of swsusp_info. +These utilities MUST NOT make any assumptions regarding the ordering of +data within the snapshot image. The contents of the image are entirely owned +by the kernel and its structure may be changed in future kernel releases. The snapshot image MUST be written to the kernel unaltered (ie. all of the image data, metadata and header MUST be written in _exactly_ the same amount, form @@ -159,7 +135,7 @@ means, such as checksums, to ensure the integrity of the snapshot image. The suspending and resuming utilities MUST lock themselves in memory, preferrably using mlockall(), before calling SNAPSHOT_FREEZE. -The suspending utility MUST check the value stored by SNAPSHOT_ATOMIC_SNAPSHOT +The suspending utility MUST check the value stored by SNAPSHOT_CREATE_IMAGE in the memory location pointed to by the last argument of ioctl() and proceed in accordance with it: 1. If the value is 1 (ie. the system memory snapshot has just been @@ -173,7 +149,7 @@ in accordance with it: image has been saved. (b) The suspending utility SHOULD NOT attempt to perform any file system operations (including reads) on the file systems - that were mounted before SNAPSHOT_ATOMIC_SNAPSHOT has been + that were mounted before SNAPSHOT_CREATE_IMAGE has been called. However, it MAY mount a file system that was not mounted at that time and perform some operations on it (eg. use it for saving the image). diff --git a/Documentation/thinkpad-acpi.txt b/Documentation/thinkpad-acpi.txt index 10c041c..4723977 100644 --- a/Documentation/thinkpad-acpi.txt +++ b/Documentation/thinkpad-acpi.txt @@ -1,7 +1,7 @@ ThinkPad ACPI Extras Driver - Version 0.17 - October 04th, 2007 + Version 0.18 + October 08th, 2007 Borislav Deianov Henrique de Moraes Holschuh @@ -231,17 +231,25 @@ sysfs notes: to this value. hotkey_enable: - Enables/disables the hot keys feature, and reports - current status of the hot keys feature. + Enables/disables the hot keys feature in the ACPI + firmware, and reports current status of the hot keys + feature. Has no effect on the NVRAM hot key polling + functionality. 0: disables the hot keys feature / feature disabled 1: enables the hot keys feature / feature enabled hotkey_mask: - bit mask to enable driver-handling and ACPI event - generation for each hot key (see above). Returns the - current status of the hot keys mask, and allows one to - modify it. + bit mask to enable driver-handling (and depending on + the firmware, ACPI event generation) for each hot key + (see above). Returns the current status of the hot keys + mask, and allows one to modify it. + + Note: when NVRAM polling is active, the firmware mask + may be slightly different than the value returned by + hotkey_mask, as the driver will retain enabled bits + for hotkeys that are under NVRAM polling even if the + firmware refuses them. hotkey_all_mask: bit mask that should enable event reporting for all @@ -257,6 +265,40 @@ sysfs notes: handled by the firmware anyway. Echo it to hotkey_mask above, to use. + hotkey_source_mask: + bit mask that selects which hot keys will the driver + poll the NVRAM for. This is auto-detected by the driver + based on the capabilities reported by the ACPI firmware, + but it can be overridden at runtime. + + Hot keys whose bits are set in both hotkey_source_mask + and also on hotkey_mask are polled for in NVRAM. Only a + few hot keys are available through CMOS NVRAM polling. + + Warning: when in NVRAM mode, the volume up/down/mute + keys are synthesized according to changes in the mixer, + so you have to use volume up or volume down to unmute, + as per the ThinkPad volume mixer user interface. When + in ACPI event mode, volume up/down/mute are reported as + separate events, but this behaviour may be corrected in + future releases of this driver, in which case the + ThinkPad volume mixer user interface semanthics will be + enforced. + + hotkey_poll_freq: + frequency in Hz for hot key polling. It must be between + 0 and 25 Hz. Polling is only carried out when strictly + needed. + + Setting hotkey_poll_freq to zero disables polling, and + will cause hot key presses that require NVRAM polling + to never be reported. + + Setting hotkey_poll_freq too low will cause repeated + pressings of the same hot key to be misreported as a + single key press, or to not even be detected at all. + The recommended polling frequency is 10Hz. + hotkey_radio_sw: if the ThinkPad has a hardware radio switch, this attribute will read 0 if the switch is in the "radios @@ -277,6 +319,21 @@ sysfs notes: May return -EPERM (write access locked out by module parameter) or -EACCES (read-only). + wakeup_reason: + Set to 1 if the system is waking up because the user + requested a bay ejection. Set to 2 if the system is + waking up because the user requested the system to + undock. Set to zero for normal wake-ups or wake-ups + due to unknown reasons. + + wakeup_hotunplug_complete: + Set to 1 if the system was waken up because of an + undock or bay ejection request, and that request + was sucessfully completed. At this point, it might + be useful to send the system back to sleep, at the + user's choice. Refer to HKEY events 0x4003 and + 0x3003, below. + input layer notes: A Hot key is mapped to a single input layer EV_KEY event, possibly @@ -427,6 +484,19 @@ Non hot-key ACPI HKEY event map: The above events are not propagated by the driver, except for legacy compatibility purposes when hotkey_report_mode is set to 1. +0x2304 System is waking up from suspend to undock +0x2305 System is waking up from suspend to eject bay +0x2404 System is waking up from hibernation to undock +0x2405 System is waking up from hibernation to eject bay + +The above events are never propagated by the driver. + +0x3003 Bay ejection (see 0x2x05) complete, can sleep again +0x4003 Undocked (see 0x2x04), can sleep again +0x5010 Brightness level changed (newer Lenovo BIOSes) + +The above events are propagated by the driver. + Compatibility notes: ibm-acpi and thinkpad-acpi 0.15 (mainline kernels before 2.6.23) never @@ -1263,3 +1333,9 @@ Sysfs interface changelog: and the hwmon class for libsensors4 (lm-sensors 3) compatibility. Moved all hwmon attributes to this new platform device. + +0x200100: Marker for thinkpad-acpi with hot key NVRAM polling + support. If you must, use it to know you should not + start an userspace NVRAM poller (allows to detect when + NVRAM is compiled out by the user because it is + unneeded/undesired in the first place). diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index a7e9fea..49f73f7 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -977,6 +977,9 @@ menu "Power management options" source "kernel/power/Kconfig" +config ARCH_SUSPEND_POSSIBLE + def_bool y + endmenu source "net/Kconfig" diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig index 2a3a7ea..e2e64af 100644 --- a/arch/blackfin/Kconfig +++ b/arch/blackfin/Kconfig @@ -925,6 +925,10 @@ endmenu menu "Power management options" source "kernel/power/Kconfig" +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on !SMP + choice prompt "Select PM Wakeup Event Source" default PM_WAKEUP_GPIO_BY_SIC_IWR diff --git a/arch/frv/Kconfig b/arch/frv/Kconfig index 43153e7..2e25b95 100644 --- a/arch/frv/Kconfig +++ b/arch/frv/Kconfig @@ -357,6 +357,11 @@ source "drivers/pcmcia/Kconfig" # should probably wait a while. menu "Power management options" + +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on !SMP + source kernel/power/Kconfig endmenu diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index c6fc405..960990d 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -2046,6 +2046,10 @@ endmenu menu "Power management options" +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on !SMP + source "kernel/power/Kconfig" endmenu diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 232c298..f8228c0 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -140,11 +140,25 @@ config DEFAULT_UIMAGE Used to allow a board to specify it wants a uImage built by default default n -config PPC64_SWSUSP +config HIBERNATE_32 bool - depends on PPC64 && (BROKEN || (PPC_PMAC64 && EXPERIMENTAL)) + depends on (PPC_PMAC && !SMP) || BROKEN default y +config HIBERNATE_64 + bool + depends on BROKEN || (PPC_PMAC64 && EXPERIMENTAL) + default y + +config ARCH_HIBERNATION_POSSIBLE + bool + depends on (PPC64 && HIBERNATE_64) || (PPC32 && HIBERNATE_32) + default y + +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 + config PPC_DCR_NATIVE bool default n diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index 496d635..3055dad 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -746,6 +746,10 @@ endmenu menu "Power management options (EXPERIMENTAL)" depends on EXPERIMENTAL && SYS_SUPPORTS_PM +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on !SMP + source kernel/power/Kconfig endmenu diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 80b7ba4..5b88aa0 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -115,7 +115,13 @@ config GENERIC_TIME_VSYSCALL config ARCH_SUPPORTS_OPROFILE bool default y +config ARCH_HIBERNATION_POSSIBLE + def_bool y + depends on !SMP || !X86_VOYAGER +config ARCH_SUSPEND_POSSIBLE + def_bool y + depends on !X86_VOYAGER config ZONE_DMA32 bool diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 0ca27c7..719c74b 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -581,25 +581,6 @@ int acpi_unregister_ioapic(acpi_handle handle, u32 gsi_base) EXPORT_SYMBOL(acpi_unregister_ioapic); -static unsigned long __init -acpi_scan_rsdp(unsigned long start, unsigned long length) -{ - unsigned long offset = 0; - unsigned long sig_len = sizeof("RSD PTR ") - 1; - - /* - * Scan all 16-byte boundaries of the physical memory region for the - * RSDP signature. - */ - for (offset = 0; offset < length; offset += 16) { - if (strncmp((char *)(phys_to_virt(start) + offset), "RSD PTR ", sig_len)) - continue; - return (start + offset); - } - - return 0; -} - static int __init acpi_parse_sbf(struct acpi_table_header *table) { struct acpi_table_boot *sb; @@ -742,27 +723,6 @@ static int __init acpi_parse_fadt(struct acpi_table_header *table) return 0; } -unsigned long __init acpi_find_rsdp(void) -{ - unsigned long rsdp_phys = 0; - - if (efi_enabled) { - if (efi.acpi20 != EFI_INVALID_TABLE_ADDR) - return efi.acpi20; - else if (efi.acpi != EFI_INVALID_TABLE_ADDR) - return efi.acpi; - } - /* - * Scan memory looking for the RSDP signature. First search EBDA (low - * memory) paragraphs and then search upper memory (E0000-FFFFF). - */ - rsdp_phys = acpi_scan_rsdp(0, 0x400); - if (!rsdp_phys) - rsdp_phys = acpi_scan_rsdp(0xE0000, 0x20000); - - return rsdp_phys; -} - #ifdef CONFIG_X86_LOCAL_APIC /* * Parse LAPIC entries in MADT diff --git a/arch/x86/kernel/cpu/cpufreq/speedstep-lib.c b/arch/x86/kernel/cpu/cpufreq/speedstep-lib.c index 76c3ab0..98d4fdb 100644 --- a/arch/x86/kernel/cpu/cpufreq/speedstep-lib.c +++ b/arch/x86/kernel/cpu/cpufreq/speedstep-lib.c @@ -189,10 +189,7 @@ static unsigned int pentium4_get_frequency(void) printk(KERN_DEBUG "speedstep-lib: couldn't detect FSB speed. Please send an e-mail to \n"); /* Multiplier. */ - if (c->x86_model < 2) - mult = msr_lo >> 27; - else - mult = msr_lo >> 24; + mult = msr_lo >> 24; dprintk("P4 - FSB %u kHz; Multiplier %u; Speed %u kHz\n", fsb, mult, (fsb * mult)); diff --git a/arch/x86/kernel/srat_32.c b/arch/x86/kernel/srat_32.c index 2a8713e..b3b2c95 100644 --- a/arch/x86/kernel/srat_32.c +++ b/arch/x86/kernel/srat_32.c @@ -276,7 +276,7 @@ int __init get_memcfg_from_srat(void) int tables = 0; int i = 0; - rsdp_address = acpi_find_rsdp(); + rsdp_address = acpi_os_get_root_pointer(); if (!rsdp_address) { printk("%s: System description tables not found\n", __FUNCTION__); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 7d6be23..8f7505d 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -125,7 +125,7 @@ static int acpi_battery_technology(struct acpi_battery *battery) return POWER_SUPPLY_TECHNOLOGY_NiMH; if (!strcasecmp("LION", battery->type)) return POWER_SUPPLY_TECHNOLOGY_LION; - if (!strcasecmp("LI-ION", battery->type)) + if (!strncasecmp("LI-ION", battery->type, 6)) return POWER_SUPPLY_TECHNOLOGY_LION; if (!strcasecmp("LiP", battery->type)) return POWER_SUPPLY_TECHNOLOGY_LIPO; diff --git a/drivers/acpi/bay.c b/drivers/acpi/bay.c index 6daf608..4777114 100644 --- a/drivers/acpi/bay.c +++ b/drivers/acpi/bay.c @@ -46,6 +46,12 @@ MODULE_LICENSE("GPL"); printk(KERN_DEBUG PREFIX "%s: %s\n", prefix, s); } static void bay_notify(acpi_handle handle, u32 event, void *data); +static const struct acpi_device_id bay_device_ids[] = { + {"LNXIOBAY", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, bay_device_ids); + struct bay { acpi_handle handle; char *name; diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 49d432d..c395bd2 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -97,11 +97,11 @@ int acpi_bus_get_status(struct acpi_device *device) } /* - * Otherwise we assume the status of our parent (unless we don't - * have one, in which case status is implied). + * According to ACPI spec some device can be present and functional + * even if the parent is not present but functional. + * In such conditions the child device should not inherit the status + * from the parent. */ - else if (device->parent) - device->status = device->parent->status; else STRUCT_TO_INT(device->status) = ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED | @@ -109,9 +109,8 @@ int acpi_bus_get_status(struct acpi_device *device) if (device->status.functional && !device->status.present) { printk(KERN_WARNING PREFIX "Device [%s] status [%08x]: " - "functional but not present; setting present\n", + "functional but not present.\n", device->pnp.bus_id, (u32) STRUCT_TO_INT(device->status)); - device->status.present = 1; } ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] status [%08x]\n", diff --git a/drivers/acpi/debug.c b/drivers/acpi/debug.c index bf513e0..6df564f 100644 --- a/drivers/acpi/debug.c +++ b/drivers/acpi/debug.c @@ -130,6 +130,63 @@ static int param_get_debug_level(char *buffer, struct kernel_param *kp) { module_param_call(debug_layer, param_set_uint, param_get_debug_layer, &acpi_dbg_layer, 0644); module_param_call(debug_level, param_set_uint, param_get_debug_level, &acpi_dbg_level, 0644); +static char trace_method_name[6]; +module_param_string(trace_method_name, trace_method_name, 6, 0644); +static unsigned int trace_debug_layer; +module_param(trace_debug_layer, uint, 0644); +static unsigned int trace_debug_level; +module_param(trace_debug_level, uint, 0644); + +static int param_set_trace_state(const char *val, struct kernel_param *kp) +{ + int result = 0; + + if (!strncmp(val, "enable", strlen("enable") - 1)) { + result = acpi_debug_trace(trace_method_name, trace_debug_level, + trace_debug_layer, 0); + if (result) + result = -EBUSY; + goto exit; + } + + if (!strncmp(val, "disable", strlen("disable") - 1)) { + int name = 0; + result = acpi_debug_trace((char *)&name, trace_debug_level, + trace_debug_layer, 0); + if (result) + result = -EBUSY; + goto exit; + } + + if (!strncmp(val, "1", 1)) { + result = acpi_debug_trace(trace_method_name, trace_debug_level, + trace_debug_layer, 1); + if (result) + result = -EBUSY; + goto exit; + } + + result = -EINVAL; +exit: + return result; +} + +static int param_get_trace_state(char *buffer, struct kernel_param *kp) +{ + if (!acpi_gbl_trace_method_name) + return sprintf(buffer, "disable"); + else { + if (acpi_gbl_trace_flags & 1) + return sprintf(buffer, "1"); + else + return sprintf(buffer, "enable"); + } + return 0; +} + +module_param_call(trace_state, param_set_trace_state, param_get_trace_state, + NULL, 0644); + /* -------------------------------------------------------------------------- FS Interface (/proc) -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 1dabdf4..b3dec21 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -51,6 +51,12 @@ static struct atomic_notifier_head dock_notifier_list; static struct platform_device *dock_device; static char dock_device_name[] = "dock"; +static const struct acpi_device_id dock_device_ids[] = { + {"LNXDOCK", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, dock_device_ids); + struct dock_station { acpi_handle handle; unsigned long last_dock_time; diff --git a/drivers/acpi/events/evgpe.c b/drivers/acpi/events/evgpe.c index e22f4a9..31cb8cd 100644 --- a/drivers/acpi/events/evgpe.c +++ b/drivers/acpi/events/evgpe.c @@ -270,18 +270,18 @@ acpi_status acpi_ev_disable_gpe(struct acpi_gpe_event_info *gpe_event_info) case ACPI_GPE_TYPE_WAKE_RUN: ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_WAKE_ENABLED); - /*lint -fallthrough */ + /* fallthrough */ case ACPI_GPE_TYPE_RUNTIME: /* Disable the requested runtime GPE */ ACPI_CLEAR_BIT(gpe_event_info->flags, ACPI_GPE_RUN_ENABLED); - status = acpi_hw_write_gpe_enable_reg(gpe_event_info); - break; + + /* fallthrough */ default: - return_ACPI_STATUS(AE_BAD_PARAMETER); + acpi_hw_write_gpe_enable_reg(gpe_event_info); } return_ACPI_STATUS(AE_OK); @@ -501,6 +501,7 @@ u32 acpi_ev_gpe_detect(struct acpi_gpe_xrupt_info * gpe_xrupt_list) * an interrupt handler. * ******************************************************************************/ +static void acpi_ev_asynch_enable_gpe(void *context); static void ACPI_SYSTEM_XFACE acpi_ev_asynch_execute_gpe_method(void *context) { @@ -576,22 +577,30 @@ static void ACPI_SYSTEM_XFACE acpi_ev_asynch_execute_gpe_method(void *context) method_node))); } } + /* Defer enabling of GPE until all notify handlers are done */ + acpi_os_execute(OSL_NOTIFY_HANDLER, acpi_ev_asynch_enable_gpe, + gpe_event_info); + return_VOID; +} - if ((local_gpe_event_info.flags & ACPI_GPE_XRUPT_TYPE_MASK) == +static void acpi_ev_asynch_enable_gpe(void *context) +{ + struct acpi_gpe_event_info *gpe_event_info = context; + acpi_status status; + if ((gpe_event_info->flags & ACPI_GPE_XRUPT_TYPE_MASK) == ACPI_GPE_LEVEL_TRIGGERED) { /* * GPE is level-triggered, we clear the GPE status bit after * handling the event. */ - status = acpi_hw_clear_gpe(&local_gpe_event_info); + status = acpi_hw_clear_gpe(gpe_event_info); if (ACPI_FAILURE(status)) { return_VOID; } } /* Enable this GPE */ - - (void)acpi_hw_write_gpe_enable_reg(&local_gpe_event_info); + (void)acpi_hw_write_gpe_enable_reg(gpe_event_info); return_VOID; } diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index e3a673a..4a6b073 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -207,8 +207,12 @@ acpi_physical_address __init acpi_os_get_root_pointer(void) "System description tables not found\n"); return 0; } - } else - return acpi_find_rsdp(); + } else { + acpi_physical_address pa = 0; + + acpi_find_root_pointer(&pa); + return pa; + } } void __iomem *acpi_os_map_memory(acpi_physical_address phys, acpi_size size) @@ -618,25 +622,6 @@ static void acpi_os_execute_deferred(struct work_struct *work) dpc->function(dpc->context); kfree(dpc); - /* Yield cpu to notify thread */ - cond_resched(); - - return; -} - -static void acpi_os_execute_notify(struct work_struct *work) -{ - struct acpi_os_dpc *dpc = container_of(work, struct acpi_os_dpc, work); - - if (!dpc) { - printk(KERN_ERR PREFIX "Invalid (NULL) context\n"); - return; - } - - dpc->function(dpc->context); - - kfree(dpc); - return; } @@ -660,7 +645,7 @@ acpi_status acpi_os_execute(acpi_execute_type type, { acpi_status status = AE_OK; struct acpi_os_dpc *dpc; - + struct workqueue_struct *queue; ACPI_DEBUG_PRINT((ACPI_DB_EXEC, "Scheduling function [%p(%p)] for deferred execution.\n", function, context)); @@ -684,20 +669,13 @@ acpi_status acpi_os_execute(acpi_execute_type type, dpc->function = function; dpc->context = context; - if (type == OSL_NOTIFY_HANDLER) { - INIT_WORK(&dpc->work, acpi_os_execute_notify); - if (!queue_work(kacpi_notify_wq, &dpc->work)) { - status = AE_ERROR; - kfree(dpc); - } - } else { - INIT_WORK(&dpc->work, acpi_os_execute_deferred); - if (!queue_work(kacpid_wq, &dpc->work)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Call to queue_work() failed.\n")); - status = AE_ERROR; - kfree(dpc); - } + INIT_WORK(&dpc->work, acpi_os_execute_deferred); + queue = (type == OSL_NOTIFY_HANDLER) ? kacpi_notify_wq : kacpid_wq; + if (!queue_work(queue, &dpc->work)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Call to queue_work() failed.\n")); + status = AE_ERROR; + kfree(dpc); } return_ACPI_STATUS(status); } diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c index 0289693..388300d 100644 --- a/drivers/acpi/pci_bind.c +++ b/drivers/acpi/pci_bind.c @@ -294,9 +294,6 @@ int acpi_pci_unbind(struct acpi_device *device) acpi_get_data(device->handle, acpi_pci_data_handler, (void **)&data); if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to get data from device %s", - acpi_device_bid(device))); result = -ENODEV; goto end; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 5b4d462..ca9e30e 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -257,6 +257,11 @@ int acpi_match_device_ids(struct acpi_device *device, const struct acpi_device_id *ids) { const struct acpi_device_id *id; + /* + * If the device is not present, the device driver is not loaded. + */ + if (!device->status.present) + return -ENODEV; if (device->flags.hardware_id) { for (id = ids; id->id[0]; id++) { @@ -941,6 +946,15 @@ static int acpi_bay_match(struct acpi_device *device){ return -ENODEV; } +/* + * acpi_dock_match - see if a device has a _DCK method + */ +static int acpi_dock_match(struct acpi_device *device) +{ + acpi_handle tmp; + return acpi_get_handle(device->handle, "_DCK", &tmp); +} + static void acpi_device_set_id(struct acpi_device *device, struct acpi_device *parent, acpi_handle handle, int type) @@ -950,6 +964,7 @@ static void acpi_device_set_id(struct acpi_device *device, char *hid = NULL; char *uid = NULL; struct acpi_compatible_id_list *cid_list = NULL; + const char *cid_add = NULL; acpi_status status; switch (type) { @@ -972,15 +987,18 @@ static void acpi_device_set_id(struct acpi_device *device, device->flags.bus_address = 1; } - if(!(info->valid & (ACPI_VALID_HID | ACPI_VALID_CID))){ - status = acpi_video_bus_match(device); - if(ACPI_SUCCESS(status)) - hid = ACPI_VIDEO_HID; + /* If we have a video/bay/dock device, add our selfdefined + HID to the CID list. Like that the video/bay/dock drivers + will get autoloaded and the device might still match + against another driver. + */ + if (ACPI_SUCCESS(acpi_video_bus_match(device))) + cid_add = ACPI_VIDEO_HID; + else if (ACPI_SUCCESS(acpi_bay_match(device))) + cid_add = ACPI_BAY_HID; + else if (ACPI_SUCCESS(acpi_dock_match(device))) + cid_add = ACPI_DOCK_HID; - status = acpi_bay_match(device); - if (ACPI_SUCCESS(status)) - hid = ACPI_BAY_HID; - } break; case ACPI_BUS_TYPE_POWER: hid = ACPI_POWER_HID; @@ -1021,11 +1039,44 @@ static void acpi_device_set_id(struct acpi_device *device, strcpy(device->pnp.unique_id, uid); device->flags.unique_id = 1; } - if (cid_list) { - device->pnp.cid_list = kmalloc(cid_list->size, GFP_KERNEL); - if (device->pnp.cid_list) - memcpy(device->pnp.cid_list, cid_list, cid_list->size); - else + if (cid_list || cid_add) { + struct acpi_compatible_id_list *list; + int size = 0; + int count = 0; + + if (cid_list) { + size = cid_list->size; + } else if (cid_add) { + size = sizeof(struct acpi_compatible_id_list); + cid_list = ACPI_ALLOCATE_ZEROED((acpi_size) size); + if (!cid_list) { + printk(KERN_ERR "Memory allocation error\n"); + kfree(buffer.pointer); + return; + } else { + cid_list->count = 0; + cid_list->size = size; + } + } + if (cid_add) + size += sizeof(struct acpi_compatible_id); + list = kmalloc(size, GFP_KERNEL); + + if (list) { + if (cid_list) { + memcpy(list, cid_list, cid_list->size); + count = cid_list->count; + } + if (cid_add) { + strncpy(list->id[count].value, cid_add, + ACPI_MAX_CID_LENGTH); + count++; + device->flags.compatible_ids = 1; + } + list->size = size; + list->count = count; + device->pnp.cid_list = list; + } else printk(KERN_ERR "Memory allocation error\n"); } @@ -1081,6 +1132,20 @@ static int acpi_bus_remove(struct acpi_device *dev, int rmdevice) } static int +acpi_is_child_device(struct acpi_device *device, + int (*matcher)(struct acpi_device *)) +{ + int result = -ENODEV; + + do { + if (ACPI_SUCCESS(matcher(device))) + return AE_OK; + } while ((device = device->parent)); + + return result; +} + +static int acpi_add_single_object(struct acpi_device **child, struct acpi_device *parent, acpi_handle handle, int type, struct acpi_bus_ops *ops) @@ -1131,10 +1196,26 @@ acpi_add_single_object(struct acpi_device **child, case ACPI_BUS_TYPE_PROCESSOR: case ACPI_BUS_TYPE_DEVICE: result = acpi_bus_get_status(device); - if (ACPI_FAILURE(result) || !device->status.present) { - result = -ENOENT; + if (ACPI_FAILURE(result) || (!device->status.present && + !device->status.functional)) { + /* + * When the device is neither present nor functional, the + * device should not be added to Linux ACPI device tree. + */ + + result = -ENODEV; goto end; } + if (!device->status.present) { + /* Bay and dock should be handled even if absent */ + if (!ACPI_SUCCESS( + acpi_is_child_device(device, acpi_bay_match)) && + !ACPI_SUCCESS( + acpi_is_child_device(device, acpi_dock_match))) { + result = -ENODEV; + goto end; + } + } break; default: STRUCT_TO_INT(device->status) = @@ -1306,7 +1387,12 @@ static int acpi_bus_scan(struct acpi_device *start, struct acpi_bus_ops *ops) * TBD: Need notifications and other detection mechanisms * in place before we can fully implement this. */ - if (child->status.present) { + /* + * When the device is not present but functional, it is also + * necessary to scan the children of this device. + */ + if ((child->status.present) || (!child->status.present && + child->status.functional)) { status = acpi_get_next_object(ACPI_TYPE_ANY, chandle, NULL, NULL); if (ACPI_SUCCESS(status)) { diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index 2c0b663..cbfa058 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c @@ -267,11 +267,6 @@ static void acpi_hibernation_leave(void) static void acpi_hibernation_finish(void) { - /* - * If ACPI is not enabled by the BIOS and the boot kernel, we need to - * enable it here. - */ - acpi_enable(); acpi_disable_wakeup_device(ACPI_STATE_S4); acpi_leave_sleep_state(ACPI_STATE_S4); diff --git a/drivers/acpi/tables/Makefile b/drivers/acpi/tables/Makefile index 0a7d7af..7385efa 100644 --- a/drivers/acpi/tables/Makefile +++ b/drivers/acpi/tables/Makefile @@ -2,6 +2,6 @@ # Makefile for all Linux ACPI interpreter subdirectories # -obj-y := tbxface.o tbinstal.o tbutils.o tbfind.o tbfadt.o +obj-y := tbxface.o tbinstal.o tbutils.o tbfind.o tbfadt.o tbxfroot.o EXTRA_CFLAGS += $(ACPI_CFLAGS) diff --git a/drivers/acpi/tables/tbxfroot.c b/drivers/acpi/tables/tbxfroot.c index cf8fa51..9ecb4b6 100644 --- a/drivers/acpi/tables/tbxfroot.c +++ b/drivers/acpi/tables/tbxfroot.c @@ -100,7 +100,7 @@ static acpi_status acpi_tb_validate_rsdp(struct acpi_table_rsdp *rsdp) /******************************************************************************* * - * FUNCTION: acpi_tb_find_rsdp + * FUNCTION: acpi_find_root_pointer * * PARAMETERS: table_address - Where the table pointer is returned * @@ -219,8 +219,6 @@ acpi_status acpi_find_root_pointer(acpi_native_uint * table_address) return_ACPI_STATUS(AE_NOT_FOUND); } -ACPI_EXPORT_SYMBOL(acpi_find_root_pointer) - /******************************************************************************* * * FUNCTION: acpi_tb_scan_memory_for_rsdp diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 5f79b44..3a0af9a 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -492,7 +492,7 @@ static int acpi_thermal_get_devices(struct acpi_thermal *tz) static int acpi_thermal_critical(struct acpi_thermal *tz) { - if (!tz || !tz->trips.critical.flags.valid || nocrt) + if (!tz || !tz->trips.critical.flags.valid) return -EINVAL; if (tz->temperature >= tz->trips.critical.temperature) { @@ -501,9 +501,6 @@ static int acpi_thermal_critical(struct acpi_thermal *tz) } else if (tz->trips.critical.flags.enabled) tz->trips.critical.flags.enabled = 0; - printk(KERN_EMERG - "Critical temperature reached (%ld C), shutting down.\n", - KELVIN_TO_CELSIUS(tz->temperature)); acpi_bus_generate_proc_event(tz->device, ACPI_THERMAL_NOTIFY_CRITICAL, tz->trips.critical.flags.enabled); acpi_bus_generate_netlink_event(tz->device->pnp.device_class, @@ -511,14 +508,20 @@ static int acpi_thermal_critical(struct acpi_thermal *tz) ACPI_THERMAL_NOTIFY_CRITICAL, tz->trips.critical.flags.enabled); - orderly_poweroff(true); + /* take no action if nocrt is set */ + if(!nocrt) { + printk(KERN_EMERG + "Critical temperature reached (%ld C), shutting down.\n", + KELVIN_TO_CELSIUS(tz->temperature)); + orderly_poweroff(true); + } return 0; } static int acpi_thermal_hot(struct acpi_thermal *tz) { - if (!tz || !tz->trips.hot.flags.valid || nocrt) + if (!tz || !tz->trips.hot.flags.valid) return -EINVAL; if (tz->temperature >= tz->trips.hot.temperature) { @@ -534,7 +537,7 @@ static int acpi_thermal_hot(struct acpi_thermal *tz) ACPI_THERMAL_NOTIFY_HOT, tz->trips.hot.flags.enabled); - /* TBD: Call user-mode "sleep(S4)" function */ + /* TBD: Call user-mode "sleep(S4)" function if nocrt is cleared */ return 0; } diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 44a0d9b..bff9669 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -56,8 +56,6 @@ #define ACPI_VIDEO_NOTIFY_ZERO_BRIGHTNESS 0x88 #define ACPI_VIDEO_NOTIFY_DISPLAY_OFF 0x89 -#define ACPI_VIDEO_HEAD_INVALID (~0u - 1) -#define ACPI_VIDEO_HEAD_END (~0u) #define MAX_NAME_LEN 20 #define ACPI_VIDEO_DISPLAY_CRT 1 @@ -76,8 +74,8 @@ static int acpi_video_bus_add(struct acpi_device *device); static int acpi_video_bus_remove(struct acpi_device *device, int type); static const struct acpi_device_id video_device_ids[] = { - {ACPI_VIDEO_HID, 0}, - {"", 0}, + { ACPI_VIDEO_HID, 0 }, + { "", 0 }, }; MODULE_DEVICE_TABLE(acpi, video_device_ids); @@ -88,7 +86,7 @@ static struct acpi_driver acpi_video_bus = { .ops = { .add = acpi_video_bus_add, .remove = acpi_video_bus_remove, - }, + }, }; struct acpi_video_bus_flags { @@ -99,26 +97,28 @@ struct acpi_video_bus_flags { }; struct acpi_video_bus_cap { - u8 _DOS:1; /*Enable/Disable output switching */ - u8 _DOD:1; /*Enumerate all devices attached to display adapter */ - u8 _ROM:1; /*Get ROM Data */ - u8 _GPD:1; /*Get POST Device */ - u8 _SPD:1; /*Set POST Device */ - u8 _VPO:1; /*Video POST Options */ + u8 _DOS:1; /* Enable/Disable output switching */ + u8 _DOD:1; /* Enumerate all devices attached to + display adapter */ + u8 _ROM:1; /* Get ROM Data */ + u8 _GPD:1; /* Get POST Device */ + u8 _SPD:1; /* Set POST Device */ + u8 _VPO:1; /* Video POST Options */ u8 reserved:2; }; struct acpi_video_device_attrib { u32 display_index:4; /* A zero-based instance of the Display */ - u32 display_port_attachment:4; /*This field differentiates the display type */ - u32 display_type:4; /*Describe the specific type in use */ - u32 vendor_specific:4; /*Chipset Vendor Specific */ - u32 bios_can_detect:1; /*BIOS can detect the device */ - u32 depend_on_vga:1; /*Non-VGA output device whose power is related to - the VGA device. */ - u32 pipe_id:3; /*For VGA multiple-head devices. */ - u32 reserved:10; /*Must be 0 */ - u32 device_id_scheme:1; /*Device ID Scheme */ + u32 display_port_attachment:4; /* This field differentiates the + display type */ + u32 display_type:4; /* Describe the specific type in use */ + u32 vendor_specific:4; /* Chipset Vendor Specific */ + u32 bios_can_detect:1; /* BIOS can detect the device */ + u32 depend_on_vga:1; /* Non-VGA output device whose power is + related to the VGA device. */ + u32 pipe_id:3; /* For VGA multiple-head devices. */ + u32 reserved:10; /* Must be 0 */ + u32 device_id_scheme:1; /* Device ID Scheme */ }; struct acpi_video_enumerated_device { @@ -286,24 +286,27 @@ static void acpi_video_switch_brightness(struct acpi_video_device *device, static int acpi_video_device_get_state(struct acpi_video_device *device, unsigned long *state); static int acpi_video_output_get(struct output_device *od); -static int acpi_video_device_set_state(struct acpi_video_device *device, int state); +static int acpi_video_device_set_state(struct acpi_video_device *device, + int state); /*backlight device sysfs support*/ static int acpi_video_get_brightness(struct backlight_device *bd) { + struct acpi_video_device *vd = bl_get_data(bd); unsigned long cur_level; - struct acpi_video_device *vd = - (struct acpi_video_device *)bl_get_data(bd); + acpi_video_device_lcd_get_level_current(vd, &cur_level); + return (int) cur_level; } static int acpi_video_set_brightness(struct backlight_device *bd) { int request_level = bd->props.brightness; - struct acpi_video_device *vd = - (struct acpi_video_device *)bl_get_data(bd); + struct acpi_video_device *vd = bl_get_data(bd); + acpi_video_device_lcd_set_level(vd, request_level); + return 0; } @@ -316,17 +319,18 @@ static struct backlight_ops acpi_backlight_ops = { static int acpi_video_output_get(struct output_device *od) { unsigned long state; - struct acpi_video_device *vd = - (struct acpi_video_device *)dev_get_drvdata(&od->dev); + struct acpi_video_device *vd = dev_get_drvdata(&od->dev); + acpi_video_device_get_state(vd, &state); + return (int)state; } static int acpi_video_output_set(struct output_device *od) { unsigned long state = od->request_state; - struct acpi_video_device *vd= - (struct acpi_video_device *)dev_get_drvdata(&od->dev); + struct acpi_video_device *vd = dev_get_drvdata(&od->dev); + return acpi_video_device_set_state(vd, state); } @@ -343,37 +347,25 @@ static struct output_properties acpi_output_properties = { static int acpi_video_device_query(struct acpi_video_device *device, unsigned long *state) { - int status; - - status = acpi_evaluate_integer(device->dev->handle, "_DGS", NULL, state); - - return status; + return acpi_evaluate_integer(device->dev->handle, "_DGS", NULL, state); } static int acpi_video_device_get_state(struct acpi_video_device *device, unsigned long *state) { - int status; - - status = acpi_evaluate_integer(device->dev->handle, "_DCS", NULL, state); - - return status; + return acpi_evaluate_integer(device->dev->handle, "_DCS", NULL, state); } static int acpi_video_device_set_state(struct acpi_video_device *device, int state) { - int status; union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; unsigned long ret; - arg0.integer.value = state; - status = acpi_evaluate_integer(device->dev->handle, "_DSS", &args, &ret); - - return status; + return acpi_evaluate_integer(device->dev->handle, "_DSS", &args, &ret); } static int @@ -384,26 +376,25 @@ acpi_video_device_lcd_query_levels(struct acpi_video_device *device, struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; - *levels = NULL; - status = acpi_evaluate_object(device->dev->handle, "_BCL", NULL, &buffer); + status = acpi_evaluate_object(device->dev->handle, "_BCL", + NULL, &buffer); if (!ACPI_SUCCESS(status)) return status; + obj = (union acpi_object *)buffer.pointer; - if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { + if (!obj || obj->type != ACPI_TYPE_PACKAGE) { printk(KERN_ERR PREFIX "Invalid _BCL data\n"); status = -EFAULT; goto err; } *levels = obj; - return 0; - err: + err: kfree(buffer.pointer); - return status; } @@ -414,7 +405,6 @@ acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level) union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - arg0.integer.value = level; if (device->cap._BCM) @@ -445,11 +435,11 @@ acpi_video_device_EDID(struct acpi_video_device *device, union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - *edid = NULL; if (!device) return -ENODEV; + if (length == 128) arg0.integer.value = 1; else if (length == 256) @@ -457,7 +447,8 @@ acpi_video_device_EDID(struct acpi_video_device *device, else return -EINVAL; - status = acpi_evaluate_object(device->dev->handle, "_DDC", &args, &buffer); + status = acpi_evaluate_object(device->dev->handle, "_DDC", &args, + &buffer); if (ACPI_FAILURE(status)) return -ENODEV; @@ -484,12 +475,12 @@ acpi_video_bus_set_POST(struct acpi_video_bus *video, unsigned long option) union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - arg0.integer.value = option; - status = acpi_evaluate_integer(video->device->handle, "_SPD", &args, &tmp); + status = acpi_evaluate_integer(video->device->handle, "_SPD", &args, + &tmp); if (ACPI_SUCCESS(status)) - status = tmp ? (-EINVAL) : (AE_OK); + status = tmp ? -EINVAL : AE_OK; return status; } @@ -497,11 +488,7 @@ acpi_video_bus_set_POST(struct acpi_video_bus *video, unsigned long option) static int acpi_video_bus_get_POST(struct acpi_video_bus *video, unsigned long *id) { - int status; - - status = acpi_evaluate_integer(video->device->handle, "_GPD", NULL, id); - - return status; + return acpi_evaluate_integer(video->device->handle, "_GPD", NULL, id); } static int @@ -510,7 +497,8 @@ acpi_video_bus_POST_options(struct acpi_video_bus *video, { int status; - status = acpi_evaluate_integer(video->device->handle, "_VPO", NULL, options); + status = acpi_evaluate_integer(video->device->handle, "_VPO", NULL, + options); *options &= 3; return status; @@ -518,23 +506,25 @@ acpi_video_bus_POST_options(struct acpi_video_bus *video, /* * Arg: - * video : video bus device pointer - * bios_flag : - * 0. The system BIOS should NOT automatically switch(toggle) - * the active display output. - * 1. The system BIOS should automatically switch (toggle) the - * active display output. No switch event. + * video : video bus device pointer + * bios_flag : + * 0. The system BIOS should NOT automatically switch + * (toggle) the active display output. + * 1. The system BIOS should automatically switch + * (toggle) the active display output. No switch event. * 2. The _DGS value should be locked. - * 3. The system BIOS should not automatically switch (toggle) the - * active display output, but instead generate the display switch - * event notify code. + * 3. The system BIOS should not automatically switch + * (toggle) the active display output, but instead + * generate the display switch event notify code. * lcd_flag : - * 0. The system BIOS should automatically control the brightness level - * of the LCD when the power changes from AC to DC - * 1. The system BIOS should NOT automatically control the brightness - * level of the LCD when the power changes from AC to DC. + * 0. The system BIOS should automatically control the + * brightness level of the LCD when the power changes + * from AC to DC + * 1. The system BIOS should NOT automatically control + * the brightness level of the LCD when the power + * changes from AC to DC. * Return Value: - * -1 wrong arg. + * -1 wrong arg. */ static int @@ -544,178 +534,199 @@ acpi_video_bus_DOS(struct acpi_video_bus *video, int bios_flag, int lcd_flag) union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - if (bios_flag < 0 || bios_flag > 3 || lcd_flag < 0 || lcd_flag > 1) { status = -1; goto Failed; } + arg0.integer.value = (lcd_flag << 2) | bios_flag; video->dos_setting = arg0.integer.value; acpi_evaluate_object(video->device->handle, "_DOS", &args, NULL); - Failed: + Failed: return status; } -/* - * Arg: - * device : video output device (LCD, CRT, ..) - * - * Return Value: - * None - * - * Find out all required AML methods defined under the output - * device. - */ - -static void acpi_video_device_find_cap(struct acpi_video_device *device) +static void +acpi_video_get_brightness_levels(struct acpi_video_device *device, + u32 *max_level) { - acpi_handle h_dummy1; int i; - u32 max_level = 0; + int count = 0; union acpi_object *obj = NULL; - struct acpi_video_device_brightness *br = NULL; + union acpi_object *o; + struct acpi_video_device_brightness *br; + *max_level = 0; - memset(&device->cap, 0, 4); - - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_ADR", &h_dummy1))) { - device->cap._ADR = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_BCL", &h_dummy1))) { - device->cap._BCL = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_BCM", &h_dummy1))) { - device->cap._BCM = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle,"_BQC",&h_dummy1))) - device->cap._BQC = 1; - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_DDC", &h_dummy1))) { - device->cap._DDC = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_DCS", &h_dummy1))) { - device->cap._DCS = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_DGS", &h_dummy1))) { - device->cap._DGS = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_DSS", &h_dummy1))) { - device->cap._DSS = 1; + if (!ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device, &obj))) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Could not query available LCD brightness level\n")); + goto out; } - if (ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device, &obj))) { - - if (obj->package.count >= 2) { - int count = 0; - union acpi_object *o; - - br = kzalloc(sizeof(*br), GFP_KERNEL); - if (!br) { - printk(KERN_ERR "can't allocate memory\n"); - } else { - br->levels = kmalloc(obj->package.count * - sizeof *(br->levels), GFP_KERNEL); - if (!br->levels) - goto out; - - for (i = 0; i < obj->package.count; i++) { - o = (union acpi_object *)&obj->package. - elements[i]; - if (o->type != ACPI_TYPE_INTEGER) { - printk(KERN_ERR PREFIX "Invalid data\n"); - continue; - } - br->levels[count] = (u32) o->integer.value; - - if (br->levels[count] > max_level) - max_level = br->levels[count]; - count++; - } - out: - if (count < 2) { - kfree(br->levels); - kfree(br); - } else { - br->count = count; - device->brightness = br; - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "found %d brightness levels\n", - count)); - } + if (obj->package.count >= 2) { + + br = kzalloc(sizeof(*br), GFP_KERNEL); + if (!br) { + printk(KERN_ERR PREFIX "can't allocate memory\n"); + goto out; + } + + br->levels = kcalloc(obj->package.count, sizeof *(br->levels), + GFP_KERNEL); + if (!br->levels) { + printk(KERN_ERR PREFIX + "can't allocate memory for levels\n"); + kfree(br); + goto out; + } + + for (i = 0; i < obj->package.count; i++) { + o = (union acpi_object *)&obj->package.elements[i]; + if (o->type != ACPI_TYPE_INTEGER) { + printk(KERN_ERR PREFIX "Invalid data\n"); + continue; } + br->levels[count] = (u32) o->integer.value; + + if (br->levels[count] > *max_level) + *max_level = br->levels[count]; + + count++; } - } else { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Could not query available LCD brightness level\n")); + if (count < 2) { + kfree(br->levels); + kfree(br); + } else { + br->count = count; + device->brightness = br; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "found %d brightness levels\n", + count)); + } } + out: kfree(obj); +} - if (device->cap._BCL && device->cap._BCM && device->cap._BQC && max_level > 0){ - unsigned long tmp; - static int count = 0; - char *name; - name = kzalloc(MAX_NAME_LEN, GFP_KERNEL); - if (!name) - return; - - sprintf(name, "acpi_video%d", count++); - acpi_video_device_lcd_get_level_current(device, &tmp); - device->backlight = backlight_device_register(name, - NULL, device, &acpi_backlight_ops); - device->backlight->props.max_brightness = max_level; - device->backlight->props.brightness = (int)tmp; - backlight_update_status(device->backlight); - - kfree(name); +static int +acpi_video_register_backlight(struct acpi_video_device *device, + u32 max_level) +{ + static atomic_t count = ATOMIC_INIT(0); + struct backlight_device *bl; + unsigned long tmp; + char name[MAX_NAME_LEN]; + + snprintf(name, sizeof(name), "acpi_video%ld", + (long)atomic_inc_return(&count) - 1); + + acpi_video_device_lcd_get_level_current(device, &tmp); + + bl = backlight_device_register(name, &device->dev->dev, device, + &acpi_backlight_ops); + if (IS_ERR(bl)) { + printk(KERN_ERR PREFIX + "can't register backlight device %s, error %ld\n", + name, PTR_ERR(bl)); + return PTR_ERR(bl); } - if (device->cap._DCS && device->cap._DSS){ - static int count = 0; - char *name; - name = kzalloc(MAX_NAME_LEN, GFP_KERNEL); - if (!name) - return; - sprintf(name, "acpi_video%d", count++); - device->output_dev = video_output_register(name, - NULL, device, &acpi_output_properties); - kfree(name); + + bl->props.max_brightness = max_level; + bl->props.brightness = (int)tmp; + + device->backlight = bl; + backlight_update_status(device->backlight); + + return 0; +} + +static int acpi_video_register_output(struct acpi_video_device *device) +{ + static atomic_t count = ATOMIC_INIT(0); + struct output_device *od; + char name[MAX_NAME_LEN]; + + snprintf(name, sizeof(name), "acpi_video%ld", + (long)atomic_inc_return(&count) - 1); + + od = video_output_register(name, &device->dev->dev, device, + &acpi_output_properties); + if (IS_ERR(od)) { + printk(KERN_ERR PREFIX + "can't register output device %s, error %ld\n", + name, PTR_ERR(od)); + return PTR_ERR(od); } - return; + + device->output_dev = od; + return 0; +} + +/* + * Arg: + * device : video output device (LCD, CRT, ..) + * + * Return Value: + * None + * + * Find out all required AML methods defined under the output + * device. + */ + +#define VIDEO_DEV_CHECK_CAP(_cap) \ + do { \ + acpi_handle h; \ + if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, \ + __stringify(_cap), &h))) \ + device->cap._cap = 1; \ + } while (0) + +static void acpi_video_device_find_cap(struct acpi_video_device *device) +{ + memset(&device->cap, 0, 4); + + VIDEO_DEV_CHECK_CAP(_ADR); + VIDEO_DEV_CHECK_CAP(_BCL); + VIDEO_DEV_CHECK_CAP(_BCM); + VIDEO_DEV_CHECK_CAP(_BQC); + VIDEO_DEV_CHECK_CAP(_DDC); + VIDEO_DEV_CHECK_CAP(_DCS); + VIDEO_DEV_CHECK_CAP(_DGS); + VIDEO_DEV_CHECK_CAP(_DSS); } /* - * Arg: - * device : video output device (VGA) + * Arg: + * device : video output device (VGA) * * Return Value: - * None + * None * * Find out all required AML methods defined under the video bus device. */ +#define VIDEO_BUS_CHECK_CAP(_cap) \ + do { \ + acpi_handle h; \ + if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, \ + __stringify(_cap), &h))) \ + video->cap._cap = 1; \ + } while (0) + static void acpi_video_bus_find_cap(struct acpi_video_bus *video) { - acpi_handle h_dummy1; - memset(&video->cap, 0, 4); - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_DOS", &h_dummy1))) { - video->cap._DOS = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_DOD", &h_dummy1))) { - video->cap._DOD = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_ROM", &h_dummy1))) { - video->cap._ROM = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_GPD", &h_dummy1))) { - video->cap._GPD = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_SPD", &h_dummy1))) { - video->cap._SPD = 1; - } - if (ACPI_SUCCESS(acpi_get_handle(video->device->handle, "_VPO", &h_dummy1))) { - video->cap._VPO = 1; - } + + VIDEO_BUS_CHECK_CAP(_DOS); + VIDEO_BUS_CHECK_CAP(_DOD); + VIDEO_BUS_CHECK_CAP(_ROM); + VIDEO_BUS_CHECK_CAP(_GPD); + VIDEO_BUS_CHECK_CAP(_SPD); + VIDEO_BUS_CHECK_CAP(_VPO); } /* @@ -727,10 +738,6 @@ static int acpi_video_bus_check(struct acpi_video_bus *video) { acpi_status status = -ENOENT; - - if (!video) - return -EINVAL; - /* Since there is no HID, CID and so on for VGA driver, we have * to check well known required nodes. */ @@ -768,10 +775,6 @@ static int acpi_video_device_info_seq_show(struct seq_file *seq, void *offset) { struct acpi_video_device *dev = seq->private; - - if (!dev) - goto end; - seq_printf(seq, "device_id: 0x%04x\n", (u32) dev->device_id); seq_printf(seq, "type: "); if (dev->flags.crt) @@ -787,7 +790,6 @@ static int acpi_video_device_info_seq_show(struct seq_file *seq, void *offset) seq_printf(seq, "known by bios: %s\n", dev->flags.bios ? "yes" : "no"); - end: return 0; } @@ -804,10 +806,6 @@ static int acpi_video_device_state_seq_show(struct seq_file *seq, void *offset) struct acpi_video_device *dev = seq->private; unsigned long state; - - if (!dev) - goto end; - status = acpi_video_device_get_state(dev, &state); seq_printf(seq, "state: "); if (ACPI_SUCCESS(status)) @@ -822,7 +820,6 @@ static int acpi_video_device_state_seq_show(struct seq_file *seq, void *offset) else seq_printf(seq, "\n"); - end: return 0; } @@ -844,7 +841,6 @@ acpi_video_device_write_state(struct file *file, char str[12] = { 0 }; u32 state = 0; - if (!dev || count + 1 > sizeof str) return -EINVAL; @@ -869,8 +865,7 @@ acpi_video_device_brightness_seq_show(struct seq_file *seq, void *offset) struct acpi_video_device *dev = seq->private; int i; - - if (!dev || !dev->brightness) { + if (!dev->brightness) { seq_printf(seq, "\n"); return 0; } @@ -901,7 +896,6 @@ acpi_video_device_write_brightness(struct file *file, unsigned int level = 0; int i; - if (!dev || !dev->brightness || count + 1 > sizeof str) return -EINVAL; @@ -933,25 +927,18 @@ static int acpi_video_device_EDID_seq_show(struct seq_file *seq, void *offset) int i; union acpi_object *edid = NULL; - - if (!dev) - goto out; - status = acpi_video_device_EDID(dev, &edid, 128); - if (ACPI_FAILURE(status)) { + if (ACPI_FAILURE(status)) status = acpi_video_device_EDID(dev, &edid, 256); - } - if (ACPI_FAILURE(status)) { + if (ACPI_FAILURE(status)) goto out; - } - if (edid && edid->type == ACPI_TYPE_BUFFER) { + if (edid && edid->type == ACPI_TYPE_BUFFER) for (i = 0; i < edid->buffer.length; i++) seq_putc(seq, edid->buffer.pointer[i]); - } - out: + out: if (!edid) seq_printf(seq, "\n"); else @@ -969,87 +956,90 @@ acpi_video_device_EDID_open_fs(struct inode *inode, struct file *file) static int acpi_video_device_add_fs(struct acpi_device *device) { - struct proc_dir_entry *entry = NULL; + struct proc_dir_entry *entry, *device_dir; struct acpi_video_device *vid_dev; - - if (!device) - return -ENODEV; - vid_dev = acpi_driver_data(device); if (!vid_dev) return -ENODEV; - if (!acpi_device_dir(device)) { - acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), - vid_dev->video->dir); - if (!acpi_device_dir(device)) - return -ENODEV; - acpi_device_dir(device)->owner = THIS_MODULE; - } + device_dir = proc_mkdir(acpi_device_bid(device), + vid_dev->video->dir); + if (!device_dir) + return -ENOMEM; + + device_dir->owner = THIS_MODULE; /* 'info' [R] */ - entry = create_proc_entry("info", S_IRUGO, acpi_device_dir(device)); + entry = create_proc_entry("info", S_IRUGO, device_dir); if (!entry) - return -ENODEV; - else { - entry->proc_fops = &acpi_video_device_info_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_dir; + + entry->proc_fops = &acpi_video_device_info_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'state' [R/W] */ - entry = - create_proc_entry("state", S_IFREG | S_IRUGO | S_IWUSR, - acpi_device_dir(device)); + entry = create_proc_entry("state", S_IFREG | S_IRUGO | S_IWUSR, + device_dir); if (!entry) - return -ENODEV; - else { - acpi_video_device_state_fops.write = acpi_video_device_write_state; - entry->proc_fops = &acpi_video_device_state_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_info; + + acpi_video_device_state_fops.write = acpi_video_device_write_state; + entry->proc_fops = &acpi_video_device_state_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'brightness' [R/W] */ - entry = - create_proc_entry("brightness", S_IFREG | S_IRUGO | S_IWUSR, - acpi_device_dir(device)); + entry = create_proc_entry("brightness", S_IFREG | S_IRUGO | S_IWUSR, + device_dir); if (!entry) - return -ENODEV; - else { - acpi_video_device_brightness_fops.write = acpi_video_device_write_brightness; - entry->proc_fops = &acpi_video_device_brightness_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_state; + + acpi_video_device_brightness_fops.write = + acpi_video_device_write_brightness; + entry->proc_fops = &acpi_video_device_brightness_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'EDID' [R] */ - entry = create_proc_entry("EDID", S_IRUGO, acpi_device_dir(device)); + entry = create_proc_entry("EDID", S_IRUGO, device_dir); if (!entry) - return -ENODEV; - else { - entry->proc_fops = &acpi_video_device_EDID_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_brightness; + + entry->proc_fops = &acpi_video_device_EDID_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; + acpi_device_dir(device) = device_dir; return 0; + + err_remove_brightness: + remove_proc_entry("brightness", device_dir); + err_remove_state: + remove_proc_entry("state", device_dir); + err_remove_info: + remove_proc_entry("info", device_dir); + err_remove_dir: + remove_proc_entry(acpi_device_bid(device), vid_dev->video->dir); + return -ENOMEM; } static int acpi_video_device_remove_fs(struct acpi_device *device) { struct acpi_video_device *vid_dev; + struct proc_dir_entry *device_dir; vid_dev = acpi_driver_data(device); if (!vid_dev || !vid_dev->video || !vid_dev->video->dir) return -ENODEV; - if (acpi_device_dir(device)) { - remove_proc_entry("info", acpi_device_dir(device)); - remove_proc_entry("state", acpi_device_dir(device)); - remove_proc_entry("brightness", acpi_device_dir(device)); - remove_proc_entry("EDID", acpi_device_dir(device)); + device_dir = acpi_device_dir(device); + if (device_dir) { + remove_proc_entry("info", device_dir); + remove_proc_entry("state", device_dir); + remove_proc_entry("brightness", device_dir); + remove_proc_entry("EDID", device_dir); remove_proc_entry(acpi_device_bid(device), vid_dev->video->dir); acpi_device_dir(device) = NULL; } @@ -1094,7 +1084,7 @@ static int acpi_video_bus_ROM_seq_show(struct seq_file *seq, void *offset) printk(KERN_INFO PREFIX "Please implement %s\n", __FUNCTION__); seq_printf(seq, "\n"); - end: + end: return 0; } @@ -1109,17 +1099,15 @@ static int acpi_video_bus_POST_info_seq_show(struct seq_file *seq, void *offset) unsigned long options; int status; - - if (!video) - goto end; - status = acpi_video_bus_POST_options(video, &options); if (ACPI_SUCCESS(status)) { if (!(options & 1)) { printk(KERN_WARNING PREFIX - "The motherboard VGA device is not listed as a possible POST device.\n"); + "The motherboard VGA device is not listed " + "as a possible POST device.\n"); printk(KERN_WARNING PREFIX - "This indicates a BIOS bug. Please contact the manufacturer.\n"); + "This indicates a BIOS bug. " + "Please contact the manufacturer.\n"); } printk("%lx\n", options); seq_printf(seq, "can POST: "); @@ -1130,7 +1118,7 @@ static int acpi_video_bus_POST_info_seq_show(struct seq_file *seq, void *offset) seq_putc(seq, '\n'); } else seq_printf(seq, "\n"); - end: + return 0; } @@ -1147,10 +1135,6 @@ static int acpi_video_bus_POST_seq_show(struct seq_file *seq, void *offset) int status; unsigned long id; - - if (!video) - goto end; - status = acpi_video_bus_get_POST(video, &id); if (!ACPI_SUCCESS(status)) { seq_printf(seq, "\n"); @@ -1158,7 +1142,7 @@ static int acpi_video_bus_POST_seq_show(struct seq_file *seq, void *offset) } seq_printf(seq, "device POSTed is <%s>\n", device_decode[id & 3]); - end: + end: return 0; } @@ -1166,7 +1150,6 @@ static int acpi_video_bus_DOS_seq_show(struct seq_file *seq, void *offset) { struct acpi_video_bus *video = seq->private; - seq_printf(seq, "DOS setting: <%d>\n", video->dos_setting); return 0; @@ -1194,7 +1177,6 @@ acpi_video_bus_write_POST(struct file *file, char str[12] = { 0 }; unsigned long opt, options; - if (!video || count + 1 > sizeof str) return -EINVAL; @@ -1256,94 +1238,91 @@ acpi_video_bus_write_DOS(struct file *file, static int acpi_video_bus_add_fs(struct acpi_device *device) { - struct proc_dir_entry *entry = NULL; - struct acpi_video_bus *video; - + struct acpi_video_bus *video = acpi_driver_data(device); + struct proc_dir_entry *device_dir; + struct proc_dir_entry *entry; - video = acpi_driver_data(device); + device_dir = proc_mkdir(acpi_device_bid(device), acpi_video_dir); + if (!device_dir) + return -ENOMEM; - if (!acpi_device_dir(device)) { - acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device), - acpi_video_dir); - if (!acpi_device_dir(device)) - return -ENODEV; - video->dir = acpi_device_dir(device); - acpi_device_dir(device)->owner = THIS_MODULE; - } + device_dir->owner = THIS_MODULE; /* 'info' [R] */ - entry = create_proc_entry("info", S_IRUGO, acpi_device_dir(device)); + entry = create_proc_entry("info", S_IRUGO, device_dir); if (!entry) - return -ENODEV; - else { - entry->proc_fops = &acpi_video_bus_info_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_dir; + + entry->proc_fops = &acpi_video_bus_info_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'ROM' [R] */ - entry = create_proc_entry("ROM", S_IRUGO, acpi_device_dir(device)); + entry = create_proc_entry("ROM", S_IRUGO, device_dir); if (!entry) - return -ENODEV; - else { - entry->proc_fops = &acpi_video_bus_ROM_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_info; + + entry->proc_fops = &acpi_video_bus_ROM_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'POST_info' [R] */ - entry = - create_proc_entry("POST_info", S_IRUGO, acpi_device_dir(device)); + entry = create_proc_entry("POST_info", S_IRUGO, device_dir); if (!entry) - return -ENODEV; - else { - entry->proc_fops = &acpi_video_bus_POST_info_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_rom; + + entry->proc_fops = &acpi_video_bus_POST_info_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'POST' [R/W] */ - entry = - create_proc_entry("POST", S_IFREG | S_IRUGO | S_IRUSR, - acpi_device_dir(device)); + entry = create_proc_entry("POST", S_IFREG | S_IRUGO | S_IWUSR, + device_dir); if (!entry) - return -ENODEV; - else { - acpi_video_bus_POST_fops.write = acpi_video_bus_write_POST; - entry->proc_fops = &acpi_video_bus_POST_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_post_info; + + acpi_video_bus_POST_fops.write = acpi_video_bus_write_POST; + entry->proc_fops = &acpi_video_bus_POST_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; /* 'DOS' [R/W] */ - entry = - create_proc_entry("DOS", S_IFREG | S_IRUGO | S_IRUSR, - acpi_device_dir(device)); + entry = create_proc_entry("DOS", S_IFREG | S_IRUGO | S_IWUSR, + device_dir); if (!entry) - return -ENODEV; - else { - acpi_video_bus_DOS_fops.write = acpi_video_bus_write_DOS; - entry->proc_fops = &acpi_video_bus_DOS_fops; - entry->data = acpi_driver_data(device); - entry->owner = THIS_MODULE; - } + goto err_remove_post; + acpi_video_bus_DOS_fops.write = acpi_video_bus_write_DOS; + entry->proc_fops = &acpi_video_bus_DOS_fops; + entry->data = acpi_driver_data(device); + entry->owner = THIS_MODULE; + + video->dir = acpi_device_dir(device) = device_dir; return 0; + + err_remove_post: + remove_proc_entry("POST", device_dir); + err_remove_post_info: + remove_proc_entry("POST_info", device_dir); + err_remove_rom: + remove_proc_entry("ROM", device_dir); + err_remove_info: + remove_proc_entry("info", device_dir); + err_remove_dir: + remove_proc_entry(acpi_device_bid(device), acpi_video_dir); + return -ENOMEM; } static int acpi_video_bus_remove_fs(struct acpi_device *device) { - struct acpi_video_bus *video; - - - video = acpi_driver_data(device); - - if (acpi_device_dir(device)) { - remove_proc_entry("info", acpi_device_dir(device)); - remove_proc_entry("ROM", acpi_device_dir(device)); - remove_proc_entry("POST_info", acpi_device_dir(device)); - remove_proc_entry("POST", acpi_device_dir(device)); - remove_proc_entry("DOS", acpi_device_dir(device)); + struct proc_dir_entry *device_dir = acpi_device_dir(device); + + if (device_dir) { + remove_proc_entry("info", device_dir); + remove_proc_entry("ROM", device_dir); + remove_proc_entry("POST_info", device_dir); + remove_proc_entry("POST", device_dir); + remove_proc_entry("DOS", device_dir); remove_proc_entry(acpi_device_bid(device), acpi_video_dir); acpi_device_dir(device) = NULL; } @@ -1356,107 +1335,148 @@ static int acpi_video_bus_remove_fs(struct acpi_device *device) -------------------------------------------------------------------------- */ /* device interface */ -static struct acpi_video_device_attrib* -acpi_video_get_device_attr(struct acpi_video_bus *video, unsigned long device_id) +static struct acpi_video_device_attrib * +acpi_video_get_device_attr(struct acpi_video_bus *video, + unsigned long device_id) { - int count; + struct acpi_video_enumerated_device *ids; + int i; + + for (i = 0; i < video->attached_count; i++) { + ids = &video->attached_array[i]; + if ((ids->value.int_val & 0xffff) == device_id) + return &ids->value.attrib; + } - for(count = 0; count < video->attached_count; count++) - if((video->attached_array[count].value.int_val & 0xffff) == device_id) - return &(video->attached_array[count].value.attrib); return NULL; } +static void +acpi_video_get_device_flags(struct acpi_video_device *device) +{ + struct acpi_video_device_attrib *attribute = + acpi_video_get_device_attr(device->video, device->device_id); + + if (attribute && attribute->device_id_scheme) { + switch (attribute->display_type) { + case ACPI_VIDEO_DISPLAY_CRT: + device->flags.crt = 1; + break; + case ACPI_VIDEO_DISPLAY_TV: + device->flags.tvout = 1; + break; + case ACPI_VIDEO_DISPLAY_DVI: + device->flags.dvi = 1; + break; + case ACPI_VIDEO_DISPLAY_LCD: + device->flags.lcd = 1; + break; + default: + device->flags.unknown = 1; + break; + } + if (attribute->bios_can_detect) + device->flags.bios = 1; + } else + device->flags.unknown = 1; +} + static int acpi_video_bus_get_one_device(struct acpi_device *device, struct acpi_video_bus *video) { + struct acpi_video_device *data; unsigned long device_id; + u32 max_brightness; int status; - struct acpi_video_device *data; - struct acpi_video_device_attrib* attribute; + int error; - if (!device || !video) - return -EINVAL; + status = acpi_evaluate_integer(device->handle, "_ADR", NULL, + &device_id); + if (!ACPI_SUCCESS(status)) + return -ENOENT; - status = - acpi_evaluate_integer(device->handle, "_ADR", NULL, &device_id); - if (ACPI_SUCCESS(status)) { + data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL); + if (!data) + return -ENOMEM; - data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL); - if (!data) - return -ENOMEM; + strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); + acpi_driver_data(device) = data; - strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); - acpi_driver_data(device) = data; + data->device_id = device_id; + data->video = video; + data->dev = device; - data->device_id = device_id; - data->video = video; - data->dev = device; + acpi_video_get_device_flags(data); + acpi_video_device_find_cap(data); + acpi_video_get_brightness_levels(data, &max_brightness); - attribute = acpi_video_get_device_attr(video, device_id); + acpi_video_device_bind(video, data); - if((attribute != NULL) && attribute->device_id_scheme) { - switch (attribute->display_type) { - case ACPI_VIDEO_DISPLAY_CRT: - data->flags.crt = 1; - break; - case ACPI_VIDEO_DISPLAY_TV: - data->flags.tvout = 1; - break; - case ACPI_VIDEO_DISPLAY_DVI: - data->flags.dvi = 1; - break; - case ACPI_VIDEO_DISPLAY_LCD: - data->flags.lcd = 1; - break; - default: - data->flags.unknown = 1; - break; - } - if(attribute->bios_can_detect) - data->flags.bios = 1; - } else - data->flags.unknown = 1; - - acpi_video_device_bind(video, data); - acpi_video_device_find_cap(data); - - status = acpi_install_notify_handler(device->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_device_notify, - data); - if (ACPI_FAILURE(status)) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, - "Error installing notify handler\n")); - if(data->brightness) - kfree(data->brightness->levels); - kfree(data->brightness); - kfree(data); - return -ENODEV; - } + if (data->cap._BCL && data->cap._BCM && data->cap._BQC && + max_brightness > 0) { + error = acpi_video_register_backlight(data, max_brightness); + if (error) + goto err_free_video_dev; + } - mutex_lock(&video->device_list_lock); - list_add_tail(&data->entry, &video->video_device_list); - mutex_unlock(&video->device_list_lock); + if (data->cap._DCS && data->cap._DSS) { + error = acpi_video_register_output(data); + if (error) + goto err_remove_backlight; + } - acpi_video_device_add_fs(device); + status = acpi_install_notify_handler(device->handle, + ACPI_DEVICE_NOTIFY, + acpi_video_device_notify, + data); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error installing notify handler\n")); + error = -EINVAL; + goto err_remove_output_dev; + } - return 0; + mutex_lock(&video->device_list_lock); + list_add_tail(&data->entry, &video->video_device_list); + mutex_unlock(&video->device_list_lock); + + error = acpi_video_device_add_fs(device); + if (error) + goto err_remove_from_list; + + return 0; + + err_remove_from_list: + mutex_lock(&video->device_list_lock); + list_del(&data->entry); + mutex_unlock(&video->device_list_lock); + acpi_remove_notify_handler(data->dev->handle, + ACPI_DEVICE_NOTIFY, + acpi_video_device_notify); + err_remove_output_dev: + video_output_unregister(data->output_dev); + err_remove_backlight: + backlight_device_unregister(data->backlight); + err_free_video_dev: + if (data->brightness) { + kfree(data->brightness->levels); + kfree(data->brightness); } + kfree(data); - return -ENOENT; + return 0; } /* * Arg: - * video : video bus device + * video : video bus device * * Return: - * none - * - * Enumerate the video device list of the video bus, + * none + * + * Enumerate the video device list of the video bus, * bind the ids with the corresponding video devices * under the video bus. */ @@ -1475,13 +1495,13 @@ static void acpi_video_device_rebind(struct acpi_video_bus *video) /* * Arg: - * video : video bus device - * device : video output device under the video - * bus + * video : video bus device + * device : video output device under the video + * bus * * Return: - * none - * + * none + * * Bind the ids with the corresponding video devices * under the video bus. */ @@ -1490,29 +1510,25 @@ static void acpi_video_device_bind(struct acpi_video_bus *video, struct acpi_video_device *device) { + struct acpi_video_enumerated_device *ids; int i; -#define IDS_VAL(i) video->attached_array[i].value.int_val -#define IDS_BIND(i) video->attached_array[i].bind_info - - for (i = 0; IDS_VAL(i) != ACPI_VIDEO_HEAD_INVALID && - i < video->attached_count; i++) { - if (device->device_id == (IDS_VAL(i) & 0xffff)) { - IDS_BIND(i) = device; + for (i = 0; i < video->attached_count; i++) { + ids = &video->attached_array[i]; + if (device->device_id == (ids->value.int_val & 0xffff)) { + ids->bind_info = device; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "device_bind %d\n", i)); } } -#undef IDS_VAL -#undef IDS_BIND } /* * Arg: - * video : video bus device + * video : video bus device * * Return: - * < 0 : error - * + * < 0 : error + * * Call _DOD to enumerate all devices attached to display adapter * */ @@ -1522,19 +1538,20 @@ static int acpi_video_device_enumerate(struct acpi_video_bus *video) int status; int count; int i; - struct acpi_video_enumerated_device *active_device_list; + struct acpi_video_enumerated_device *active_list; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *dod = NULL; union acpi_object *obj; - status = acpi_evaluate_object(video->device->handle, "_DOD", NULL, &buffer); + status = acpi_evaluate_object(video->device->handle, "_DOD", NULL, + &buffer); if (!ACPI_SUCCESS(status)) { ACPI_EXCEPTION((AE_INFO, status, "Evaluating _DOD")); return status; } dod = buffer.pointer; - if (!dod || (dod->type != ACPI_TYPE_PACKAGE)) { + if (!dod || dod->type != ACPI_TYPE_PACKAGE) { ACPI_EXCEPTION((AE_INFO, status, "Invalid _DOD data")); status = -EFAULT; goto out; @@ -1543,13 +1560,10 @@ static int acpi_video_device_enumerate(struct acpi_video_bus *video) ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d video heads in _DOD\n", dod->package.count)); - active_device_list = kmalloc((1 + - dod->package.count) * - sizeof(struct - acpi_video_enumerated_device), - GFP_KERNEL); - - if (!active_device_list) { + active_list = kcalloc(1 + dod->package.count, + sizeof(struct acpi_video_enumerated_device), + GFP_KERNEL); + if (!active_list) { status = -ENOMEM; goto out; } @@ -1559,35 +1573,36 @@ static int acpi_video_device_enumerate(struct acpi_video_bus *video) obj = &dod->package.elements[i]; if (obj->type != ACPI_TYPE_INTEGER) { - printk(KERN_ERR PREFIX "Invalid _DOD data\n"); - active_device_list[i].value.int_val = - ACPI_VIDEO_HEAD_INVALID; + printk(KERN_ERR PREFIX + "Invalid _DOD data in element %d\n", i); + continue; } - active_device_list[i].value.int_val = obj->integer.value; - active_device_list[i].bind_info = NULL; + + active_list[count].value.int_val = obj->integer.value; + active_list[count].bind_info = NULL; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "dod element[%d] = %d\n", i, (int)obj->integer.value)); count++; } - active_device_list[count].value.int_val = ACPI_VIDEO_HEAD_END; kfree(video->attached_array); - video->attached_array = active_device_list; + video->attached_array = active_list; video->attached_count = count; - out: + + out: kfree(buffer.pointer); return status; } /* * Arg: - * video : video bus device - * event : notify event + * video : video bus device + * event : notify event * * Return: - * < 0 : error - * + * < 0 : error + * * 1. Find out the current active output device. * 2. Identify the next output device to switch to. * 3. call _DSS to do actual switch. @@ -1687,47 +1702,39 @@ static void acpi_video_switch_brightness(struct acpi_video_device *device, int event) { unsigned long level_current, level_next; + acpi_video_device_lcd_get_level_current(device, &level_current); level_next = acpi_video_get_next_level(device, level_current, event); acpi_video_device_lcd_set_level(device, level_next); } -static int +static void acpi_video_bus_get_devices(struct acpi_video_bus *video, struct acpi_device *device) { - int status = 0; + int error; struct acpi_device *dev; acpi_video_device_enumerate(video); list_for_each_entry(dev, &device->children, node) { - - status = acpi_video_bus_get_one_device(dev, video); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Cant attach device")); - continue; - } + error = acpi_video_bus_get_one_device(dev, video); + if (error) + printk(KERN_ERR PREFIX "Can't attach device"); } - return status; } static int acpi_video_bus_put_one_device(struct acpi_video_device *device) { - acpi_status status; - struct acpi_video_bus *video; - + struct acpi_video_bus *video = device->video; - if (!device || !device->video) + if (!video) return -ENOENT; - video = device->video; - acpi_video_device_remove_fs(device->dev); - - status = acpi_remove_notify_handler(device->dev->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_device_notify); + acpi_remove_notify_handler(device->dev->handle, + ACPI_DEVICE_NOTIFY, + acpi_video_device_notify); backlight_device_unregister(device->backlight); video_output_unregister(device->output_dev); @@ -1776,7 +1783,7 @@ static int acpi_video_bus_stop_devices(struct acpi_video_bus *video) static void acpi_video_bus_notify(acpi_handle handle, u32 event, void *data) { struct acpi_video_bus *video = data; - struct acpi_device *device = NULL; + struct acpi_device *device; struct input_dev *input; int keycode; @@ -1829,14 +1836,12 @@ static void acpi_video_bus_notify(acpi_handle handle, u32 event, void *data) input_sync(input); input_report_key(input, keycode, 0); input_sync(input); - - return; } static void acpi_video_device_notify(acpi_handle handle, u32 event, void *data) { struct acpi_video_device *video_device = data; - struct acpi_device *device = NULL; + struct acpi_device *device; struct acpi_video_bus *bus; struct input_dev *input; int keycode; @@ -1885,30 +1890,33 @@ static void acpi_video_device_notify(acpi_handle handle, u32 event, void *data) input_sync(input); input_report_key(input, keycode, 0); input_sync(input); - - return; } -static int instance; static int acpi_video_bus_add(struct acpi_device *device) { + static atomic_t instance = ATOMIC_INIT(0); + acpi_status status; struct acpi_video_bus *video; struct input_dev *input; int error; + int i; video = kzalloc(sizeof(struct acpi_video_bus), GFP_KERNEL); if (!video) return -ENOMEM; + mutex_init(&video->device_list_lock); + INIT_LIST_HEAD(&video->video_device_list); + video->device = device; + /* a hack to fix the duplicate name "VID" problem on T61 */ if (!strcmp(device->pnp.bus_id, "VID")) { - if (instance) - device->pnp.bus_id[3] = '0' + instance; - instance ++; + i = atomic_inc_return(&instance) - 1; + if (i) + device->pnp.bus_id[3] = '0' + i; } - video->device = device; strcpy(acpi_device_name(device), ACPI_VIDEO_BUS_NAME); strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); acpi_driver_data(device) = video; @@ -1922,9 +1930,6 @@ static int acpi_video_bus_add(struct acpi_device *device) if (error) goto err_free_video; - mutex_init(&video->device_list_lock); - INIT_LIST_HEAD(&video->video_device_list); - acpi_video_bus_get_devices(video, device); acpi_video_bus_start_devices(video); @@ -1994,25 +1999,20 @@ static int acpi_video_bus_add(struct acpi_device *device) static int acpi_video_bus_remove(struct acpi_device *device, int type) { - acpi_status status = 0; - struct acpi_video_bus *video = NULL; - + struct acpi_video_bus *video = acpi_driver_data(device); - if (!device || !acpi_driver_data(device)) + if (!video) return -EINVAL; - video = acpi_driver_data(device); - acpi_video_bus_stop_devices(video); - - status = acpi_remove_notify_handler(video->device->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_bus_notify); - + acpi_remove_notify_handler(video->device->handle, + ACPI_DEVICE_NOTIFY, + acpi_video_bus_notify); acpi_video_bus_put_devices(video); acpi_video_bus_remove_fs(device); input_unregister_device(video->input); + kfree(video->attached_array); kfree(video); @@ -2023,7 +2023,6 @@ static int __init acpi_video_init(void) { int result = 0; - /* acpi_dbg_level = 0xFFFFFFFF; acpi_dbg_layer = 0x08000000; @@ -2045,12 +2044,8 @@ static int __init acpi_video_init(void) static void __exit acpi_video_exit(void) { - acpi_bus_unregister_driver(&acpi_video_bus); - remove_proc_entry(ACPI_VIDEO_CLASS, acpi_root_dir); - - return; } module_init(acpi_video_init); diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 44504e6..e3484bc 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,7 +1,7 @@ obj-y := shutdown.o obj-$(CONFIG_PM) += sysfs.o obj-$(CONFIG_PM_SLEEP) += main.o -obj-$(CONFIG_PM_TRACE) += trace.o +obj-$(CONFIG_PM_TRACE_RTC) += trace.o ifeq ($(CONFIG_DEBUG_DRIVER),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 3bed412..7dbc4a8 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -1,13 +1,13 @@ config CPU_IDLE bool "CPU idle PM support" + default ACPI 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 you're using an ACPI-enabled platform, you should say Y here. config CPU_IDLE_GOV_LADDER bool diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b5e67c0..b1f9a40 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -219,6 +219,25 @@ config THINKPAD_ACPI_BAY If you are not sure, say Y here. +config THINKPAD_ACPI_HOTKEY_POLL + bool "Suport NVRAM polling for hot keys" + depends on THINKPAD_ACPI + default y + ---help--- + Some thinkpad models benefit from NVRAM polling to detect a few of + the hot key press events. If you know your ThinkPad model does not + need to do NVRAM polling to support any of the hot keys you use, + unselecting this option will save about 1kB of memory. + + ThinkPads T40 and newer, R52 and newer, and X31 and newer are + unlikely to need NVRAM polling in their latest BIOS versions. + + NVRAM polling can detect at most the following keys: ThinkPad/Access + IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute, + Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12). + + If you are not sure, say Y here. The driver enables polling only if + it is strictly necessary to do so. config ATMEL_SSC tristate "Device driver for Atmel SSC peripheral" diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index ab23a32..4defc31 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -21,11 +21,13 @@ * 02110-1301, USA. */ -#define IBM_VERSION "0.17" -#define TPACPI_SYSFS_VERSION 0x020000 +#define TPACPI_VERSION "0.18" +#define TPACPI_SYSFS_VERSION 0x020100 /* * Changelog: + * 2007-10-20 changelog trimmed down + * * 2007-03-27 0.14 renamed to thinkpad_acpi and moved to * drivers/misc. * @@ -33,89 +35,219 @@ * 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 + * + * 2005-01-16 0.9 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 "thinkpad_acpi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + + +/* 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 + +/* NVRAM Addresses */ +enum tp_nvram_addr { + TP_NVRAM_ADDR_HK2 = 0x57, + TP_NVRAM_ADDR_THINKLIGHT = 0x58, + TP_NVRAM_ADDR_VIDEO = 0x59, + TP_NVRAM_ADDR_BRIGHTNESS = 0x5e, + TP_NVRAM_ADDR_MIXER = 0x60, +}; -MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); -MODULE_DESCRIPTION(IBM_DESC); -MODULE_VERSION(IBM_VERSION); -MODULE_LICENSE("GPL"); +/* NVRAM bit masks */ +enum { + TP_NVRAM_MASK_HKT_THINKPAD = 0x08, + TP_NVRAM_MASK_HKT_ZOOM = 0x20, + TP_NVRAM_MASK_HKT_DISPLAY = 0x40, + TP_NVRAM_MASK_HKT_HIBERNATE = 0x80, + TP_NVRAM_MASK_THINKLIGHT = 0x10, + TP_NVRAM_MASK_HKT_DISPEXPND = 0x30, + TP_NVRAM_MASK_HKT_BRIGHTNESS = 0x20, + TP_NVRAM_MASK_LEVEL_BRIGHTNESS = 0x0f, + TP_NVRAM_POS_LEVEL_BRIGHTNESS = 0, + TP_NVRAM_MASK_MUTE = 0x40, + TP_NVRAM_MASK_HKT_VOLUME = 0x80, + TP_NVRAM_MASK_LEVEL_VOLUME = 0x0f, + TP_NVRAM_POS_LEVEL_VOLUME = 0, +}; -/* Please remove this in year 2009 */ -MODULE_ALIAS("ibm_acpi"); +/* ACPI HIDs */ +#define TPACPI_ACPI_HKEY_HID "IBM0068" -/* - * DMI matching for module autoloading - * - * See http://thinkwiki.org/wiki/List_of_DMI_IDs - * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads - * - * Only models listed in thinkwiki will be supported, so add yours - * if it is not there yet. +/* Input IDs */ +#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */ +#define TPACPI_HKEY_INPUT_VERSION 0x4101 + + +/**************************************************************************** + * Main driver */ -#define IBM_BIOS_MODULE_ALIAS(__type) \ - MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW") -/* Non-ancient thinkpads */ -MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*"); -MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*"); +#define TPACPI_NAME "thinkpad" +#define TPACPI_DESC "ThinkPad ACPI Extras" +#define TPACPI_FILE TPACPI_NAME "_acpi" +#define TPACPI_URL "http://ibm-acpi.sf.net/" +#define TPACPI_MAIL "ibm-acpi-devel@lists.sourceforge.net" + +#define TPACPI_PROC_DIR "ibm" +#define TPACPI_ACPI_EVENT_PREFIX "ibm" +#define TPACPI_DRVR_NAME TPACPI_FILE +#define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon" + +#define TPACPI_MAX_ACPI_ARGS 3 + +/* Debugging */ +#define TPACPI_LOG TPACPI_FILE ": " +#define TPACPI_ERR KERN_ERR TPACPI_LOG +#define TPACPI_NOTICE KERN_NOTICE TPACPI_LOG +#define TPACPI_INFO KERN_INFO TPACPI_LOG +#define TPACPI_DEBUG KERN_DEBUG TPACPI_LOG + +#define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_ALL 0xffff +#define TPACPI_DBG_INIT 0x0001 +#define TPACPI_DBG_EXIT 0x0002 +#define dbg_printk(a_dbg_level, format, arg...) \ + do { if (dbg_level & a_dbg_level) \ + printk(TPACPI_DEBUG "%s: " format, __func__ , ## arg); \ + } while (0) +#ifdef CONFIG_THINKPAD_ACPI_DEBUG +#define vdbg_printk(a_dbg_level, format, arg...) \ + dbg_printk(a_dbg_level, format, ## arg) +static const char *str_supported(int is_supported); +#else +#define vdbg_printk(a_dbg_level, format, arg...) +#endif -/* Ancient thinkpad BIOSes have to be identified by - * BIOS type or model number, and there are far less - * BIOS types than model numbers... */ -IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]"); -IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]"); -IBM_BIOS_MODULE_ALIAS("K[U,X-Z]"); +#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))) + + +/**************************************************************************** + * Driver-wide structs and misc. variables + */ + +struct ibm_struct; -#define __unused __attribute__ ((unused)) +struct tp_acpi_drv_struct { + const struct acpi_device_id *hid; + struct acpi_driver *driver; + + void (*notify) (struct ibm_struct *, u32); + acpi_handle *handle; + u32 type; + struct acpi_device *device; +}; + +struct ibm_struct { + char *name; + + int (*read) (char *); + int (*write) (char *); + void (*exit) (void); + void (*resume) (void); + void (*suspend) (pm_message_t state); + + struct list_head all_drivers; + + struct tp_acpi_drv_struct *acpi; + + struct { + u8 acpi_driver_registered:1; + u8 acpi_notify_installed:1; + u8 proc_created:1; + u8 init_called:1; + u8 experimental:1; + } flags; +}; + +struct ibm_init_struct { + char param[32]; + + int (*init) (struct ibm_init_struct *); + struct ibm_struct *data; +}; + +static struct { +#ifdef CONFIG_THINKPAD_ACPI_BAY + u32 bay_status:1; + u32 bay_eject:1; + u32 bay_status2:1; + u32 bay_eject2:1; +#endif + u32 bluetooth:1; + u32 hotkey:1; + u32 hotkey_mask:1; + u32 hotkey_wlsw:1; + u32 light:1; + u32 light_status:1; + u32 bright_16levels:1; + u32 wan:1; + u32 fan_ctrl_status_undef:1; + u32 input_device_registered:1; + u32 platform_drv_registered:1; + u32 platform_drv_attrs_registered:1; + u32 sensors_pdrv_registered:1; + u32 sensors_pdrv_attrs_registered:1; + u32 sensors_pdev_attrs_registered:1; + u32 hotkey_poll_active:1; +} tp_features; + +struct thinkpad_id_data { + unsigned int vendor; /* ThinkPad vendor: + * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */ + + char *bios_version_str; /* Something like 1ZET51WW (1.03z) */ + char *ec_version_str; /* Something like 1ZHT51WW-1.04a */ + + u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */ + u16 ec_model; + + char *model_str; +}; +static struct thinkpad_id_data thinkpad_id; static enum { TPACPI_LIFE_INIT = 0, @@ -123,6 +255,9 @@ static enum { TPACPI_LIFE_EXITING, } tpacpi_lifecycle; +static int experimental; +static u32 dbg_level; + /**************************************************************************** **************************************************************************** * @@ -137,13 +272,13 @@ static enum { static acpi_handle root_handle; -#define IBM_HANDLE(object, parent, paths...) \ +#define TPACPI_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 */ +TPACPI_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 */ @@ -152,20 +287,16 @@ IBM_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */ "\\_SB.PCI0.LPC.EC", /* all others */ ); -IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ -IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ +TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */ +TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */ - -/************************************************************************* - * Misc ACPI handles - */ - -IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */ +TPACPI_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 */ +TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */ "^HKEY", /* R30, R31 */ "HKEY", /* all others */ ); /* 570 */ @@ -180,7 +311,7 @@ static int acpi_evalf(acpi_handle handle, { char *fmt0 = fmt; struct acpi_object_list params; - union acpi_object in_objs[IBM_MAX_ACPI_ARGS]; + union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS]; struct acpi_buffer result, *resultp; union acpi_object out_obj; acpi_status status; @@ -190,7 +321,7 @@ static int acpi_evalf(acpi_handle handle, int quiet; if (!*fmt) { - printk(IBM_ERR "acpi_evalf() called with empty format\n"); + printk(TPACPI_ERR "acpi_evalf() called with empty format\n"); return 0; } @@ -215,7 +346,7 @@ static int acpi_evalf(acpi_handle handle, break; /* add more types as needed */ default: - printk(IBM_ERR "acpi_evalf() called " + printk(TPACPI_ERR "acpi_evalf() called " "with invalid format character '%c'\n", c); return 0; } @@ -242,29 +373,19 @@ static int acpi_evalf(acpi_handle handle, break; /* add more types as needed */ default: - printk(IBM_ERR "acpi_evalf() called " + printk(TPACPI_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", + printk(TPACPI_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) +static int acpi_ec_read(int i, u8 *p) { int v; @@ -318,6 +439,10 @@ static int issue_thinkpad_cmos_command(int cmos_cmd) * ACPI device model */ +#define TPACPI_ACPIHANDLE_INIT(object) \ + drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ + object##_paths, ARRAY_SIZE(object##_paths), &object##_path) + static void drv_acpi_handle_init(char *name, acpi_handle *handle, acpi_handle parent, char **paths, int num_paths, char **path) @@ -372,25 +497,27 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); if (rc < 0) { - printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n", + printk(TPACPI_ERR "acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc); return -ENODEV; } acpi_driver_data(ibm->acpi->device) = ibm; sprintf(acpi_device_class(ibm->acpi->device), "%s/%s", - IBM_ACPI_EVENT_PREFIX, + TPACPI_ACPI_EVENT_PREFIX, ibm->name); status = acpi_install_notify_handler(*ibm->acpi->handle, ibm->acpi->type, dispatch_acpi_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); + printk(TPACPI_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); + printk(TPACPI_ERR + "acpi_install_notify_handler(%s) failed: %d\n", + ibm->name, status); } return -ENODEV; } @@ -414,18 +541,18 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL); if (!ibm->acpi->driver) { - printk(IBM_ERR "kzalloc(ibm->driver) failed\n"); + printk(TPACPI_ERR "kzalloc(ibm->driver) failed\n"); return -ENOMEM; } - sprintf(ibm->acpi->driver->name, "%s_%s", IBM_NAME, ibm->name); + sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name); ibm->acpi->driver->ids = ibm->acpi->hid; ibm->acpi->driver->ops.add = &tpacpi_device_add; rc = acpi_bus_register_driver(ibm->acpi->driver); if (rc < 0) { - printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n", + printk(TPACPI_ERR "acpi_bus_register_driver(%s) failed: %d\n", ibm->name, rc); kfree(ibm->acpi->driver); ibm->acpi->driver = NULL; @@ -470,7 +597,7 @@ static int dispatch_procfs_read(char *page, char **start, off_t off, } static int dispatch_procfs_write(struct file *file, - const char __user * userbuf, + const char __user *userbuf, unsigned long count, void *data) { struct ibm_struct *ibm = data; @@ -530,7 +657,22 @@ static struct platform_device *tpacpi_sensors_pdev; static struct device *tpacpi_hwmon; static struct input_dev *tpacpi_inputdev; static struct mutex tpacpi_inputdev_send_mutex; +static LIST_HEAD(tpacpi_all_drivers); +static int tpacpi_suspend_handler(struct platform_device *pdev, + pm_message_t state) +{ + struct ibm_struct *ibm, *itmp; + + list_for_each_entry_safe(ibm, itmp, + &tpacpi_all_drivers, + all_drivers) { + if (ibm->suspend) + (ibm->suspend)(state); + } + + return 0; +} static int tpacpi_resume_handler(struct platform_device *pdev) { @@ -548,107 +690,36 @@ static int tpacpi_resume_handler(struct platform_device *pdev) static struct platform_driver tpacpi_pdriver = { .driver = { - .name = IBM_DRVR_NAME, + .name = TPACPI_DRVR_NAME, .owner = THIS_MODULE, }, + .suspend = tpacpi_suspend_handler, .resume = tpacpi_resume_handler, }; static struct platform_driver tpacpi_hwmon_pdriver = { .driver = { - .name = IBM_HWMON_DRVR_NAME, + .name = TPACPI_HWMON_DRVR_NAME, .owner = THIS_MODULE, }, }; /************************************************************************* - * thinkpad-acpi driver attributes + * sysfs support helpers */ -/* interface_version --------------------------------------------------- */ -static ssize_t tpacpi_driver_interface_version_show( - struct device_driver *drv, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION); -} - -static DRIVER_ATTR(interface_version, S_IRUGO, - tpacpi_driver_interface_version_show, NULL); - -/* debug_level --------------------------------------------------------- */ -static ssize_t tpacpi_driver_debug_show(struct device_driver *drv, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level); -} - -static ssize_t tpacpi_driver_debug_store(struct device_driver *drv, - const char *buf, size_t count) -{ - unsigned long t; - - if (parse_strtoul(buf, 0xffff, &t)) - return -EINVAL; - - dbg_level = t; - - return count; -} - -static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, - tpacpi_driver_debug_show, tpacpi_driver_debug_store); - -/* version ------------------------------------------------------------- */ -static ssize_t tpacpi_driver_version_show(struct device_driver *drv, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION); -} - -static DRIVER_ATTR(version, S_IRUGO, - tpacpi_driver_version_show, NULL); - -/* --------------------------------------------------------------------- */ - -static struct driver_attribute* tpacpi_driver_attributes[] = { - &driver_attr_debug_level, &driver_attr_version, - &driver_attr_interface_version, +struct attribute_set { + unsigned int members, max_members; + struct attribute_group group; }; -static int __init tpacpi_create_driver_attributes(struct device_driver *drv) -{ - int i, res; - - i = 0; - res = 0; - while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) { - res = driver_create_file(drv, tpacpi_driver_attributes[i]); - i++; - } - - return res; -} - -static void tpacpi_remove_driver_attributes(struct device_driver *drv) -{ - int i; - - for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++) - driver_remove_file(drv, tpacpi_driver_attributes[i]); -} - -/************************************************************************* - * sysfs support helpers - */ - struct attribute_set_obj { struct attribute_set s; struct attribute *a; } __attribute__((packed)); static struct attribute_set *create_attr_set(unsigned int max_members, - const char* name) + const char *name) { struct attribute_set_obj *sobj; @@ -668,8 +739,11 @@ static struct attribute_set *create_attr_set(unsigned int max_members, return &sobj->s; } +#define destroy_attr_set(_set) \ + kfree(_set); + /* not multi-threaded safe, use it in a single thread per set */ -static int add_to_attr_set(struct attribute_set* s, struct attribute *attr) +static int add_to_attr_set(struct attribute_set *s, struct attribute *attr) { if (!s || !attr) return -EINVAL; @@ -683,7 +757,7 @@ static int add_to_attr_set(struct attribute_set* s, struct attribute *attr) return 0; } -static int add_many_to_attr_set(struct attribute_set* s, +static int add_many_to_attr_set(struct attribute_set *s, struct attribute **attr, unsigned int count) { @@ -698,12 +772,15 @@ static int add_many_to_attr_set(struct attribute_set* s, return 0; } -static void delete_attr_set(struct attribute_set* s, struct kobject *kobj) +static void delete_attr_set(struct attribute_set *s, struct kobject *kobj) { sysfs_remove_group(kobj, &s->group); destroy_attr_set(s); } +#define register_attr_set_with_sysfs(_attr_set, _kobj) \ + sysfs_create_group(_kobj, &_attr_set->group) + static int parse_strtoul(const char *buf, unsigned long max, unsigned long *value) { @@ -720,6 +797,84 @@ static int parse_strtoul(const char *buf, return 0; } +/************************************************************************* + * thinkpad-acpi driver attributes + */ + +/* interface_version --------------------------------------------------- */ +static ssize_t tpacpi_driver_interface_version_show( + struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION); +} + +static DRIVER_ATTR(interface_version, S_IRUGO, + tpacpi_driver_interface_version_show, NULL); + +/* debug_level --------------------------------------------------------- */ +static ssize_t tpacpi_driver_debug_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level); +} + +static ssize_t tpacpi_driver_debug_store(struct device_driver *drv, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 0xffff, &t)) + return -EINVAL; + + dbg_level = t; + + return count; +} + +static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, + tpacpi_driver_debug_show, tpacpi_driver_debug_store); + +/* version ------------------------------------------------------------- */ +static ssize_t tpacpi_driver_version_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s v%s\n", + TPACPI_DESC, TPACPI_VERSION); +} + +static DRIVER_ATTR(version, S_IRUGO, + tpacpi_driver_version_show, NULL); + +/* --------------------------------------------------------------------- */ + +static struct driver_attribute *tpacpi_driver_attributes[] = { + &driver_attr_debug_level, &driver_attr_version, + &driver_attr_interface_version, +}; + +static int __init tpacpi_create_driver_attributes(struct device_driver *drv) +{ + int i, res; + + i = 0; + res = 0; + while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) { + res = driver_create_file(drv, tpacpi_driver_attributes[i]); + i++; + } + + return res; +} + +static void tpacpi_remove_driver_attributes(struct device_driver *drv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++) + driver_remove_file(drv, tpacpi_driver_attributes[i]); +} + /**************************************************************************** **************************************************************************** * @@ -734,17 +889,17 @@ static int parse_strtoul(const char *buf, static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm) { - printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION); - printk(IBM_INFO "%s\n", IBM_URL); + printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION); + printk(TPACPI_INFO "%s\n", TPACPI_URL); - printk(IBM_INFO "ThinkPad BIOS %s, EC %s\n", + printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n", (thinkpad_id.bios_version_str) ? thinkpad_id.bios_version_str : "unknown", (thinkpad_id.ec_version_str) ? thinkpad_id.ec_version_str : "unknown"); if (thinkpad_id.vendor && thinkpad_id.model_str) - printk(IBM_INFO "%s %s\n", + printk(TPACPI_INFO "%s %s\n", (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ? "IBM" : ((thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) ? @@ -758,8 +913,8 @@ static int thinkpad_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); + len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC); + len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION); return len; } @@ -773,15 +928,129 @@ static struct ibm_struct thinkpad_acpi_driver_data = { * Hotkey subdriver */ +enum { /* hot key scan codes (derived from ACPI DSDT) */ + TP_ACPI_HOTKEYSCAN_FNF1 = 0, + TP_ACPI_HOTKEYSCAN_FNF2, + TP_ACPI_HOTKEYSCAN_FNF3, + TP_ACPI_HOTKEYSCAN_FNF4, + TP_ACPI_HOTKEYSCAN_FNF5, + TP_ACPI_HOTKEYSCAN_FNF6, + TP_ACPI_HOTKEYSCAN_FNF7, + TP_ACPI_HOTKEYSCAN_FNF8, + TP_ACPI_HOTKEYSCAN_FNF9, + TP_ACPI_HOTKEYSCAN_FNF10, + TP_ACPI_HOTKEYSCAN_FNF11, + TP_ACPI_HOTKEYSCAN_FNF12, + TP_ACPI_HOTKEYSCAN_FNBACKSPACE, + TP_ACPI_HOTKEYSCAN_FNINSERT, + TP_ACPI_HOTKEYSCAN_FNDELETE, + TP_ACPI_HOTKEYSCAN_FNHOME, + TP_ACPI_HOTKEYSCAN_FNEND, + TP_ACPI_HOTKEYSCAN_FNPAGEUP, + TP_ACPI_HOTKEYSCAN_FNPAGEDOWN, + TP_ACPI_HOTKEYSCAN_FNSPACE, + TP_ACPI_HOTKEYSCAN_VOLUMEUP, + TP_ACPI_HOTKEYSCAN_VOLUMEDOWN, + TP_ACPI_HOTKEYSCAN_MUTE, + TP_ACPI_HOTKEYSCAN_THINKPAD, +}; + +enum { /* Keys available through NVRAM polling */ + TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U, + TPACPI_HKEY_NVRAM_GOOD_MASK = 0x00fb8000U, +}; + +enum { /* Positions of some of the keys in hotkey masks */ + TP_ACPI_HKEY_DISPSWTCH_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF7, + TP_ACPI_HKEY_DISPXPAND_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF8, + TP_ACPI_HKEY_HIBERNATE_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF12, + TP_ACPI_HKEY_BRGHTUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNHOME, + TP_ACPI_HKEY_BRGHTDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNEND, + TP_ACPI_HKEY_THNKLGHT_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP, + TP_ACPI_HKEY_ZOOM_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE, + TP_ACPI_HKEY_VOLUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP, + TP_ACPI_HKEY_VOLDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN, + TP_ACPI_HKEY_MUTE_MASK = 1 << TP_ACPI_HOTKEYSCAN_MUTE, + TP_ACPI_HKEY_THINKPAD_MASK = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD, +}; + +enum { /* NVRAM to ACPI HKEY group map */ + TP_NVRAM_HKEY_GROUP_HK2 = TP_ACPI_HKEY_THINKPAD_MASK | + TP_ACPI_HKEY_ZOOM_MASK | + TP_ACPI_HKEY_DISPSWTCH_MASK | + TP_ACPI_HKEY_HIBERNATE_MASK, + TP_NVRAM_HKEY_GROUP_BRIGHTNESS = TP_ACPI_HKEY_BRGHTUP_MASK | + TP_ACPI_HKEY_BRGHTDWN_MASK, + TP_NVRAM_HKEY_GROUP_VOLUME = TP_ACPI_HKEY_VOLUP_MASK | + TP_ACPI_HKEY_VOLDWN_MASK | + TP_ACPI_HKEY_MUTE_MASK, +}; + +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL +struct tp_nvram_state { + u16 thinkpad_toggle:1; + u16 zoom_toggle:1; + u16 display_toggle:1; + u16 thinklight_toggle:1; + u16 hibernate_toggle:1; + u16 displayexp_toggle:1; + u16 display_state:1; + u16 brightness_toggle:1; + u16 volume_toggle:1; + u16 mute:1; + + u8 brightness_level; + u8 volume_level; +}; + +static struct task_struct *tpacpi_hotkey_task; +static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */ +static int hotkey_poll_freq = 10; /* Hz */ +static struct mutex hotkey_thread_mutex; +static struct mutex hotkey_thread_data_mutex; +static unsigned int hotkey_config_change; + +#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ + +#define hotkey_source_mask 0U + +#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ + +static struct mutex hotkey_mutex; + +static enum { /* Reasons for waking up */ + TP_ACPI_WAKEUP_NONE = 0, /* None or unknown */ + TP_ACPI_WAKEUP_BAYEJ, /* Bay ejection request */ + TP_ACPI_WAKEUP_UNDOCK, /* Undock request */ +} hotkey_wakeup_reason; + +static int hotkey_autosleep_ack; + static int hotkey_orig_status; static u32 hotkey_orig_mask; static u32 hotkey_all_mask; static u32 hotkey_reserved_mask; +static u32 hotkey_mask; + +static unsigned int hotkey_report_mode; static u16 *hotkey_keycode_map; static struct attribute_set *hotkey_dev_attributes; +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL +#define HOTKEY_CONFIG_CRITICAL_START \ + do { \ + mutex_lock(&hotkey_thread_data_mutex); \ + hotkey_config_change++; \ + } while (0); +#define HOTKEY_CONFIG_CRITICAL_END \ + mutex_unlock(&hotkey_thread_data_mutex); +#else +#define HOTKEY_CONFIG_CRITICAL_START +#define HOTKEY_CONFIG_CRITICAL_END +#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ + static int hotkey_get_wlsw(int *status) { if (!acpi_evalf(hkey_handle, status, "WLSW", "d")) @@ -789,15 +1058,377 @@ static int hotkey_get_wlsw(int *status) return 0; } +/* + * Call with hotkey_mutex held + */ +static int hotkey_mask_get(void) +{ + u32 m; + + if (tp_features.hotkey_mask) { + if (!acpi_evalf(hkey_handle, &m, "DHKN", "d")) + return -EIO; + hotkey_mask = m | (hotkey_source_mask & hotkey_mask); + } + + return 0; +} + +/* + * Call with hotkey_mutex held + */ +static int hotkey_mask_set(u32 mask) +{ + int i; + int rc = 0; + + if (tp_features.hotkey_mask) { + HOTKEY_CONFIG_CRITICAL_START + for (i = 0; i < 32; i++) { + u32 m = 1 << i; + if (!acpi_evalf(hkey_handle, + NULL, "MHKM", "vdd", i + 1, + !!(mask & m))) { + rc = -EIO; + break; + } else { + hotkey_mask = (hotkey_mask & ~m) | (mask & m); + } + } + HOTKEY_CONFIG_CRITICAL_END + + /* hotkey_mask_get must be called unconditionally below */ + if (!hotkey_mask_get() && !rc && + (hotkey_mask & ~hotkey_source_mask) != + (mask & ~hotkey_source_mask)) { + printk(TPACPI_NOTICE + "requested hot key mask 0x%08x, but " + "firmware forced it to 0x%08x\n", + mask, hotkey_mask); + } + } + + return rc; +} + +static int hotkey_status_get(int *status) +{ + if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) + return -EIO; + + return 0; +} + +static int hotkey_status_set(int status) +{ + if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) + return -EIO; + + return 0; +} + +static void tpacpi_input_send_radiosw(void) +{ + int wlsw; + + mutex_lock(&tpacpi_inputdev_send_mutex); + + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { + input_report_switch(tpacpi_inputdev, + SW_RADIO, !!wlsw); + input_sync(tpacpi_inputdev); + } + + mutex_unlock(&tpacpi_inputdev_send_mutex); +} + +static void tpacpi_input_send_key(unsigned int scancode) +{ + unsigned int keycode; + + keycode = hotkey_keycode_map[scancode]; + + if (keycode != KEY_RESERVED) { + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_key(tpacpi_inputdev, keycode, 1); + if (keycode == KEY_UNKNOWN) + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, + scancode); + input_sync(tpacpi_inputdev); + + input_report_key(tpacpi_inputdev, keycode, 0); + if (keycode == KEY_UNKNOWN) + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, + scancode); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + } +} + +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL +static struct tp_acpi_drv_struct ibm_hotkey_acpidriver; + +static void tpacpi_hotkey_send_key(unsigned int scancode) +{ + tpacpi_input_send_key(scancode); + if (hotkey_report_mode < 2) { + acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device, + 0x80, 0x1001 + scancode); + } +} + +static void hotkey_read_nvram(struct tp_nvram_state *n, u32 m) +{ + u8 d; + + if (m & TP_NVRAM_HKEY_GROUP_HK2) { + d = nvram_read_byte(TP_NVRAM_ADDR_HK2); + n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD); + n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM); + n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY); + n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE); + } + if (m & TP_ACPI_HKEY_THNKLGHT_MASK) { + d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT); + n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT); + } + if (m & TP_ACPI_HKEY_DISPXPAND_MASK) { + d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO); + n->displayexp_toggle = + !!(d & TP_NVRAM_MASK_HKT_DISPEXPND); + } + if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) { + d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS); + n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS) + >> TP_NVRAM_POS_LEVEL_BRIGHTNESS; + n->brightness_toggle = + !!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS); + } + if (m & TP_NVRAM_HKEY_GROUP_VOLUME) { + d = nvram_read_byte(TP_NVRAM_ADDR_MIXER); + n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME) + >> TP_NVRAM_POS_LEVEL_VOLUME; + n->mute = !!(d & TP_NVRAM_MASK_MUTE); + n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME); + } +} + +#define TPACPI_COMPARE_KEY(__scancode, __member) \ + do { \ + if ((mask & (1 << __scancode)) && \ + oldn->__member != newn->__member) \ + tpacpi_hotkey_send_key(__scancode); \ + } while (0) + +#define TPACPI_MAY_SEND_KEY(__scancode) \ + do { if (mask & (1 << __scancode)) \ + tpacpi_hotkey_send_key(__scancode); } while (0) + +static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, + struct tp_nvram_state *newn, + u32 mask) +{ + if (oldn == newn) + return; + + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle); + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle); + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle); + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle); + + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle); + + TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle); + + /* handle volume */ + if (oldn->volume_toggle != newn->volume_toggle) { + if (oldn->mute != newn->mute) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE); + } + if (oldn->volume_level > newn->volume_level) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + } else if (oldn->volume_level < newn->volume_level) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + } else if (oldn->mute == newn->mute) { + /* repeated key presses that didn't change state */ + if (newn->mute) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE); + } else if (newn->volume_level != 0) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP); + } else { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN); + } + } + } + + /* handle brightness */ + if (oldn->brightness_toggle != newn->brightness_toggle) { + if (oldn->brightness_level < newn->brightness_level) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + } else if (oldn->brightness_level > newn->brightness_level) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + } else { + /* repeated key presses that didn't change state */ + if (newn->brightness_level != 0) { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME); + } else { + TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND); + } + } + } +} + +#undef TPACPI_COMPARE_KEY +#undef TPACPI_MAY_SEND_KEY + +static int hotkey_kthread(void *data) +{ + struct tp_nvram_state s[2]; + u32 mask; + unsigned int si, so; + unsigned long t; + unsigned int change_detector, must_reset; + + mutex_lock(&hotkey_thread_mutex); + + if (tpacpi_lifecycle == TPACPI_LIFE_EXITING) + goto exit; + + set_freezable(); + + so = 0; + si = 1; + t = 0; + + /* Initial state for compares */ + mutex_lock(&hotkey_thread_data_mutex); + change_detector = hotkey_config_change; + mask = hotkey_source_mask & hotkey_mask; + mutex_unlock(&hotkey_thread_data_mutex); + hotkey_read_nvram(&s[so], mask); + + while (!kthread_should_stop() && hotkey_poll_freq) { + if (t == 0) + t = 1000/hotkey_poll_freq; + t = msleep_interruptible(t); + if (unlikely(kthread_should_stop())) + break; + must_reset = try_to_freeze(); + if (t > 0 && !must_reset) + continue; + + mutex_lock(&hotkey_thread_data_mutex); + if (must_reset || hotkey_config_change != change_detector) { + /* forget old state on thaw or config change */ + si = so; + t = 0; + change_detector = hotkey_config_change; + } + mask = hotkey_source_mask & hotkey_mask; + mutex_unlock(&hotkey_thread_data_mutex); + + hotkey_read_nvram(&s[si], mask); + hotkey_compare_and_issue_event(&s[so], &s[si], mask); + + so = si; + si ^= 1; + } + +exit: + mutex_unlock(&hotkey_thread_mutex); + return 0; +} + +static void hotkey_poll_stop_sync(void) +{ + if (tpacpi_hotkey_task) { + if (frozen(tpacpi_hotkey_task) || + freezing(tpacpi_hotkey_task)) + thaw_process(tpacpi_hotkey_task); + + kthread_stop(tpacpi_hotkey_task); + tpacpi_hotkey_task = NULL; + mutex_lock(&hotkey_thread_mutex); + /* at this point, the thread did exit */ + mutex_unlock(&hotkey_thread_mutex); + } +} + +/* call with hotkey_mutex held */ +static void hotkey_poll_setup(int may_warn) +{ + if ((hotkey_source_mask & hotkey_mask) != 0 && + hotkey_poll_freq > 0 && + (tpacpi_inputdev->users > 0 || hotkey_report_mode < 2)) { + if (!tpacpi_hotkey_task) { + tpacpi_hotkey_task = kthread_run(hotkey_kthread, + NULL, + TPACPI_FILE "d"); + if (IS_ERR(tpacpi_hotkey_task)) { + tpacpi_hotkey_task = NULL; + printk(TPACPI_ERR + "could not create kernel thread " + "for hotkey polling\n"); + } + } + } else { + hotkey_poll_stop_sync(); + if (may_warn && + hotkey_source_mask != 0 && hotkey_poll_freq == 0) { + printk(TPACPI_NOTICE + "hot keys 0x%08x require polling, " + "which is currently disabled\n", + hotkey_source_mask); + } + } +} + +static void hotkey_poll_setup_safe(int may_warn) +{ + mutex_lock(&hotkey_mutex); + hotkey_poll_setup(may_warn); + mutex_unlock(&hotkey_mutex); +} + +static int hotkey_inputdev_open(struct input_dev *dev) +{ + switch (tpacpi_lifecycle) { + case TPACPI_LIFE_INIT: + /* + * hotkey_init will call hotkey_poll_setup_safe + * at the appropriate moment + */ + return 0; + case TPACPI_LIFE_EXITING: + return -EBUSY; + case TPACPI_LIFE_RUNNING: + hotkey_poll_setup_safe(0); + return 0; + } + + /* Should only happen if tpacpi_lifecycle is corrupt */ + BUG(); + return -EBUSY; +} + +static void hotkey_inputdev_close(struct input_dev *dev) +{ + /* disable hotkey polling when possible */ + if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING) + hotkey_poll_setup_safe(0); +} +#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ + /* sysfs hotkey enable ------------------------------------------------- */ static ssize_t hotkey_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { int res, status; - u32 mask; - res = hotkey_get(&status, &mask); + res = hotkey_status_get(&status); if (res) return res; @@ -809,15 +1440,12 @@ static ssize_t hotkey_enable_store(struct device *dev, const char *buf, size_t count) { unsigned long t; - int res, status; - u32 mask; + int res; if (parse_strtoul(buf, 1, &t)) return -EINVAL; - res = hotkey_get(&status, &mask); - if (!res) - res = hotkey_set(t, mask); + res = hotkey_status_set(t); return (res) ? res : count; } @@ -831,14 +1459,15 @@ static ssize_t hotkey_mask_show(struct device *dev, struct device_attribute *attr, char *buf) { - int res, status; - u32 mask; + int res; - res = hotkey_get(&status, &mask); - if (res) - return res; + if (mutex_lock_interruptible(&hotkey_mutex)) + return -ERESTARTSYS; + res = hotkey_mask_get(); + mutex_unlock(&hotkey_mutex); - return snprintf(buf, PAGE_SIZE, "0x%08x\n", mask); + return (res)? + res : snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_mask); } static ssize_t hotkey_mask_store(struct device *dev, @@ -846,15 +1475,21 @@ static ssize_t hotkey_mask_store(struct device *dev, const char *buf, size_t count) { unsigned long t; - int res, status; - u32 mask; + int res; if (parse_strtoul(buf, 0xffffffffUL, &t)) return -EINVAL; - res = hotkey_get(&status, &mask); - if (!res) - hotkey_set(status, t); + if (mutex_lock_interruptible(&hotkey_mutex)) + return -ERESTARTSYS; + + res = hotkey_mask_set(t); + +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + hotkey_poll_setup(1); +#endif + + mutex_unlock(&hotkey_mutex); return (res) ? res : count; } @@ -890,7 +1525,8 @@ static ssize_t hotkey_all_mask_show(struct device *dev, struct device_attribute *attr, char *buf) { - return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_all_mask); + return snprintf(buf, PAGE_SIZE, "0x%08x\n", + hotkey_all_mask | hotkey_source_mask); } static struct device_attribute dev_attr_hotkey_all_mask = @@ -902,13 +1538,86 @@ static ssize_t hotkey_recommended_mask_show(struct device *dev, char *buf) { return snprintf(buf, PAGE_SIZE, "0x%08x\n", - hotkey_all_mask & ~hotkey_reserved_mask); + (hotkey_all_mask | hotkey_source_mask) + & ~hotkey_reserved_mask); } static struct device_attribute dev_attr_hotkey_recommended_mask = __ATTR(hotkey_recommended_mask, S_IRUGO, hotkey_recommended_mask_show, NULL); +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + +/* sysfs hotkey hotkey_source_mask ------------------------------------- */ +static ssize_t hotkey_source_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_source_mask); +} + +static ssize_t hotkey_source_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 0xffffffffUL, &t) || + ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0)) + return -EINVAL; + + if (mutex_lock_interruptible(&hotkey_mutex)) + return -ERESTARTSYS; + + HOTKEY_CONFIG_CRITICAL_START + hotkey_source_mask = t; + HOTKEY_CONFIG_CRITICAL_END + + hotkey_poll_setup(1); + + mutex_unlock(&hotkey_mutex); + + return count; +} + +static struct device_attribute dev_attr_hotkey_source_mask = + __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO, + hotkey_source_mask_show, hotkey_source_mask_store); + +/* sysfs hotkey hotkey_poll_freq --------------------------------------- */ +static ssize_t hotkey_poll_freq_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_poll_freq); +} + +static ssize_t hotkey_poll_freq_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long t; + + if (parse_strtoul(buf, 25, &t)) + return -EINVAL; + + if (mutex_lock_interruptible(&hotkey_mutex)) + return -ERESTARTSYS; + + hotkey_poll_freq = t; + + hotkey_poll_setup(1); + mutex_unlock(&hotkey_mutex); + + return count; +} + +static struct device_attribute dev_attr_hotkey_poll_freq = + __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO, + hotkey_poll_freq_show, hotkey_poll_freq_store); + +#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */ + /* sysfs hotkey radio_sw ----------------------------------------------- */ static ssize_t hotkey_radio_sw_show(struct device *dev, struct device_attribute *attr, @@ -937,6 +1646,29 @@ static ssize_t hotkey_report_mode_show(struct device *dev, static struct device_attribute dev_attr_hotkey_report_mode = __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL); +/* sysfs wakeup reason ------------------------------------------------- */ +static ssize_t hotkey_wakeup_reason_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason); +} + +static struct device_attribute dev_attr_hotkey_wakeup_reason = + __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL); + +/* sysfs wakeup hotunplug_complete ------------------------------------- */ +static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack); +} + +static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete = + __ATTR(wakeup_hotunplug_complete, S_IRUGO, + hotkey_wakeup_hotunplug_complete_show, NULL); + /* --------------------------------------------------------------------- */ static struct attribute *hotkey_attributes[] __initdata = { @@ -950,6 +1682,12 @@ static struct attribute *hotkey_mask_attributes[] __initdata = { &dev_attr_hotkey_bios_mask.attr, &dev_attr_hotkey_all_mask.attr, &dev_attr_hotkey_recommended_mask.attr, +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + &dev_attr_hotkey_source_mask.attr, + &dev_attr_hotkey_poll_freq.attr, +#endif + &dev_attr_hotkey_wakeup_reason.attr, + &dev_attr_hotkey_wakeup_hotunplug_complete.attr, }; static int __init hotkey_init(struct ibm_init_struct *iibm) @@ -1013,10 +1751,17 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n"); BUG_ON(!tpacpi_inputdev); + BUG_ON(tpacpi_inputdev->open != NULL || + tpacpi_inputdev->close != NULL); - IBM_ACPIHANDLE_INIT(hkey); + TPACPI_ACPIHANDLE_INIT(hkey); mutex_init(&hotkey_mutex); +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + mutex_init(&hotkey_thread_mutex); + mutex_init(&hotkey_thread_data_mutex); +#endif + /* hotkey not supported on 570 */ tp_features.hotkey = hkey_handle != NULL; @@ -1024,7 +1769,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) str_supported(tp_features.hotkey)); if (tp_features.hotkey) { - hotkey_dev_attributes = create_attr_set(8, NULL); + hotkey_dev_attributes = create_attr_set(12, NULL); if (!hotkey_dev_attributes) return -ENOMEM; res = add_many_to_attr_set(hotkey_dev_attributes, @@ -1038,15 +1783,15 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) for HKEY interface version 0x100 */ if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) { if ((hkeyv >> 8) != 1) { - printk(IBM_ERR "unknown version of the " + printk(TPACPI_ERR "unknown version of the " "HKEY interface: 0x%x\n", hkeyv); - printk(IBM_ERR "please report this to %s\n", - IBM_MAIL); + printk(TPACPI_ERR "please report this to %s\n", + TPACPI_MAIL); } else { /* * MHKV 0x100 in A31, R40, R40e, * T4x, X31, and later - * */ + */ tp_features.hotkey_mask = 1; } } @@ -1057,25 +1802,42 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (tp_features.hotkey_mask) { if (!acpi_evalf(hkey_handle, &hotkey_all_mask, "MHKA", "qd")) { - printk(IBM_ERR + printk(TPACPI_ERR "missing MHKA handler, " "please report this to %s\n", - IBM_MAIL); - hotkey_all_mask = 0x080cU; /* FN+F12, FN+F4, FN+F3 */ + TPACPI_MAIL); + /* FN+F12, FN+F4, FN+F3 */ + hotkey_all_mask = 0x080cU; } } - res = hotkey_get(&hotkey_orig_status, &hotkey_orig_mask); + /* hotkey_source_mask *must* be zero for + * the first hotkey_mask_get */ + res = hotkey_status_get(&hotkey_orig_status); if (!res && tp_features.hotkey_mask) { - res = add_many_to_attr_set(hotkey_dev_attributes, - hotkey_mask_attributes, - ARRAY_SIZE(hotkey_mask_attributes)); + res = hotkey_mask_get(); + hotkey_orig_mask = hotkey_mask; + if (!res) { + res = add_many_to_attr_set( + hotkey_dev_attributes, + hotkey_mask_attributes, + ARRAY_SIZE(hotkey_mask_attributes)); + } } +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK + & ~hotkey_all_mask; + + vdbg_printk(TPACPI_DBG_INIT, + "hotkey source mask 0x%08x, polling freq %d\n", + hotkey_source_mask, hotkey_poll_freq); +#endif + /* Not all thinkpads have a hardware radio switch */ if (!res && acpi_evalf(hkey_handle, &status, "WLSW", "qd")) { tp_features.hotkey_wlsw = 1; - printk(IBM_INFO + printk(TPACPI_INFO "radio switch found; radios are %s\n", enabled(status, 0)); res = add_to_attr_set(hotkey_dev_attributes, @@ -1094,7 +1856,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL); if (!hotkey_keycode_map) { - printk(IBM_ERR "failed to allocate memory for key map\n"); + printk(TPACPI_ERR + "failed to allocate memory for key map\n"); return -ENOMEM; } @@ -1133,7 +1896,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) dbg_printk(TPACPI_DBG_INIT, "enabling hot key handling\n"); - res = hotkey_set(1, (hotkey_all_mask & ~hotkey_reserved_mask) + res = hotkey_status_set(1); + if (res) + return res; + res = hotkey_mask_set(((hotkey_all_mask | hotkey_source_mask) + & ~hotkey_reserved_mask) | hotkey_orig_mask); if (res) return res; @@ -1142,6 +1909,13 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) "legacy hot key reporting over procfs %s\n", (hotkey_report_mode < 2) ? "enabled" : "disabled"); + +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + tpacpi_inputdev->open = &hotkey_inputdev_open; + tpacpi_inputdev->close = &hotkey_inputdev_close; + + hotkey_poll_setup_safe(1); +#endif } return (tp_features.hotkey)? 0 : 1; @@ -1149,13 +1923,19 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) static void hotkey_exit(void) { - int res; +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + hotkey_poll_stop_sync(); +#endif if (tp_features.hotkey) { - dbg_printk(TPACPI_DBG_EXIT, "restoring original hotkey mask\n"); - res = hotkey_set(hotkey_orig_status, hotkey_orig_mask); - if (res) - printk(IBM_ERR "failed to restore hotkey to BIOS defaults\n"); + dbg_printk(TPACPI_DBG_EXIT, + "restoring original hot key mask\n"); + /* no short-circuit boolean operator below! */ + if ((hotkey_mask_set(hotkey_orig_mask) | + hotkey_status_set(hotkey_orig_status)) != 0) + printk(TPACPI_ERR + "failed to restore hot key mask " + "to BIOS defaults\n"); } if (hotkey_dev_attributes) { @@ -1164,62 +1944,28 @@ static void hotkey_exit(void) } } -static void tpacpi_input_send_key(unsigned int scancode, - unsigned int keycode) -{ - if (keycode != KEY_RESERVED) { - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_report_key(tpacpi_inputdev, keycode, 1); - if (keycode == KEY_UNKNOWN) - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, - scancode); - input_sync(tpacpi_inputdev); - - input_report_key(tpacpi_inputdev, keycode, 0); - if (keycode == KEY_UNKNOWN) - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, - scancode); - input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); - } -} - -static void tpacpi_input_send_radiosw(void) -{ - int wlsw; - - mutex_lock(&tpacpi_inputdev_send_mutex); - - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { - input_report_switch(tpacpi_inputdev, - SW_RADIO, !!wlsw); - input_sync(tpacpi_inputdev); - } - - mutex_unlock(&tpacpi_inputdev_send_mutex); -} - static void hotkey_notify(struct ibm_struct *ibm, u32 event) { u32 hkey; - unsigned int keycode, scancode; + unsigned int scancode; int send_acpi_ev; int ignore_acpi_ev; + int unk_ev; if (event != 0x80) { - printk(IBM_ERR "unknown HKEY notification event %d\n", event); + printk(TPACPI_ERR + "unknown HKEY notification event %d\n", event); /* forward it to userspace, maybe it knows how to handle it */ - acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class, - ibm->acpi->device->dev.bus_id, - event, 0); + acpi_bus_generate_netlink_event( + ibm->acpi->device->pnp.device_class, + ibm->acpi->device->dev.bus_id, + event, 0); return; } while (1) { if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) { - printk(IBM_ERR "failed to retrieve HKEY event\n"); + printk(TPACPI_ERR "failed to retrieve HKEY event\n"); return; } @@ -1228,8 +1974,9 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) return; } - send_acpi_ev = 0; + send_acpi_ev = 1; ignore_acpi_ev = 0; + unk_ev = 0; switch (hkey >> 12) { case 1: @@ -1237,104 +1984,129 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) scancode = hkey & 0xfff; if (scancode > 0 && scancode < 0x21) { scancode--; - keycode = hotkey_keycode_map[scancode]; - tpacpi_input_send_key(scancode, keycode); + if (!(hotkey_source_mask & (1 << scancode))) { + tpacpi_input_send_key(scancode); + send_acpi_ev = 0; + } else { + ignore_acpi_ev = 1; + } } else { - printk(IBM_ERR - "hotkey 0x%04x out of range for keyboard map\n", - hkey); - send_acpi_ev = 1; + unk_ev = 1; } break; - case 5: - /* 0x5000-0x5FFF: LID */ - /* we don't handle it through this path, just - * eat up known LID events */ - if (hkey != 0x5001 && hkey != 0x5002) { - printk(IBM_ERR - "unknown LID-related HKEY event: 0x%04x\n", - hkey); - send_acpi_ev = 1; + case 2: + /* Wakeup reason */ + switch (hkey) { + case 0x2304: /* suspend, undock */ + case 0x2404: /* hibernation, undock */ + hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK; + ignore_acpi_ev = 1; + break; + case 0x2305: /* suspend, bay eject */ + case 0x2405: /* hibernation, bay eject */ + hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ; + ignore_acpi_ev = 1; + break; + default: + unk_ev = 1; + } + if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) { + printk(TPACPI_INFO + "woke up due to a hot-unplug " + "request...\n"); + } + break; + case 3: + /* bay-related wakeups */ + if (hkey == 0x3003) { + hotkey_autosleep_ack = 1; + printk(TPACPI_INFO + "bay ejected\n"); + } else { + unk_ev = 1; + } + break; + case 4: + /* dock-related wakeups */ + if (hkey == 0x4003) { + hotkey_autosleep_ack = 1; + printk(TPACPI_INFO + "undocked\n"); } else { + unk_ev = 1; + } + break; + case 5: + /* 0x5000-0x5FFF: On screen display helpers */ + switch (hkey) { + case 0x5010: + /* Lenovo Vista BIOS: brightness changed */ + break; + case 0x5001: + case 0x5002: + /* LID switch events. Do not propagate */ ignore_acpi_ev = 1; + break; + default: + unk_ev = 1; } break; case 7: /* 0x7000-0x7FFF: misc */ if (tp_features.hotkey_wlsw && hkey == 0x7000) { tpacpi_input_send_radiosw(); + send_acpi_ev = 0; break; } /* fallthrough to default */ default: - /* case 2: dock-related */ - /* 0x2305 - T43 waking up due to bay lever eject while aslept */ - /* case 3: ultra-bay related. maybe bay in dock? */ - /* 0x3003 - T43 after wake up by bay lever eject (0x2305) */ - printk(IBM_NOTICE "unhandled HKEY event 0x%04x\n", hkey); - send_acpi_ev = 1; + unk_ev = 1; + } + if (unk_ev) { + printk(TPACPI_NOTICE + "unhandled HKEY event 0x%04x\n", hkey); } /* Legacy events */ - if (!ignore_acpi_ev && (send_acpi_ev || hotkey_report_mode < 2)) { - acpi_bus_generate_proc_event(ibm->acpi->device, event, hkey); + if (!ignore_acpi_ev && + (send_acpi_ev || hotkey_report_mode < 2)) { + acpi_bus_generate_proc_event(ibm->acpi->device, + event, hkey); } /* netlink events */ if (!ignore_acpi_ev && send_acpi_ev) { - acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class, - ibm->acpi->device->dev.bus_id, - event, hkey); + acpi_bus_generate_netlink_event( + ibm->acpi->device->pnp.device_class, + ibm->acpi->device->dev.bus_id, + event, hkey); } } } -static void hotkey_resume(void) -{ - tpacpi_input_send_radiosw(); -} - -/* - * Call with hotkey_mutex held - */ -static int hotkey_get(int *status, u32 *mask) +static void hotkey_suspend(pm_message_t state) { - if (!acpi_evalf(hkey_handle, status, "DHKC", "d")) - return -EIO; - - if (tp_features.hotkey_mask) - if (!acpi_evalf(hkey_handle, mask, "DHKN", "d")) - return -EIO; - - return 0; + /* Do these on suspend, we get the events on early resume! */ + hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE; + hotkey_autosleep_ack = 0; } -/* - * Call with hotkey_mutex held - */ -static int hotkey_set(int status, u32 mask) +static void hotkey_resume(void) { - int i; - - if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status)) - return -EIO; - - if (tp_features.hotkey_mask) - for (i = 0; i < 32; i++) { - int bit = ((1 << i) & mask) != 0; - if (!acpi_evalf(hkey_handle, - NULL, "MHKM", "vdd", i + 1, bit)) - return -EIO; - } - - return 0; + if (hotkey_mask_get()) + printk(TPACPI_ERR + "error while trying to read hot key mask " + "from firmware\n"); + tpacpi_input_send_radiosw(); +#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL + hotkey_poll_setup_safe(0); +#endif } /* procfs -------------------------------------------------------------- */ static int hotkey_read(char *p) { int res, status; - u32 mask; int len = 0; if (!tp_features.hotkey) { @@ -1344,14 +2116,16 @@ static int hotkey_read(char *p) if (mutex_lock_interruptible(&hotkey_mutex)) return -ERESTARTSYS; - res = hotkey_get(&status, &mask); + res = hotkey_status_get(&status); + if (!res) + res = hotkey_mask_get(); mutex_unlock(&hotkey_mutex); if (res) return res; len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); if (tp_features.hotkey_mask) { - len += sprintf(p + len, "mask:\t\t0x%08x\n", mask); + len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_mask); len += sprintf(p + len, "commands:\tenable, disable, reset, \n"); } else { @@ -1367,7 +2141,6 @@ static int hotkey_write(char *buf) int res, status; u32 mask; char *cmd; - int do_cmd = 0; if (!tp_features.hotkey) return -ENODEV; @@ -1375,9 +2148,8 @@ static int hotkey_write(char *buf) if (mutex_lock_interruptible(&hotkey_mutex)) return -ERESTARTSYS; - res = hotkey_get(&status, &mask); - if (res) - goto errexit; + status = -1; + mask = hotkey_mask; res = 0; while ((cmd = next_cmd(&buf))) { @@ -1396,11 +2168,12 @@ static int hotkey_write(char *buf) res = -EINVAL; goto errexit; } - do_cmd = 1; } + if (status != -1) + res = hotkey_status_set(status); - if (do_cmd) - res = hotkey_set(status, mask); + if (!res && mask != hotkey_mask) + res = hotkey_mask_set(mask); errexit: mutex_unlock(&hotkey_mutex); @@ -1408,7 +2181,7 @@ errexit: } static const struct acpi_device_id ibm_htk_device_ids[] = { - {IBM_HKEY_HID, 0}, + {TPACPI_ACPI_HKEY_HID, 0}, {"", 0}, }; @@ -1425,6 +2198,7 @@ static struct ibm_struct hotkey_driver_data = { .write = hotkey_write, .exit = hotkey_exit, .resume = hotkey_resume, + .suspend = hotkey_suspend, .acpi = &ibm_hotkey_acpidriver, }; @@ -1432,6 +2206,16 @@ static struct ibm_struct hotkey_driver_data = { * Bluetooth subdriver */ +enum { + /* ACPI GBDC/SBDC bits */ + TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ + TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ + TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ +}; + +static int bluetooth_get_radiosw(void); +static int bluetooth_set_radiosw(int radio_on); + /* sysfs bluetooth enable ---------------------------------------------- */ static ssize_t bluetooth_enable_show(struct device *dev, struct device_attribute *attr, @@ -1483,7 +2267,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n"); - IBM_ACPIHANDLE_INIT(hkey); + TPACPI_ACPIHANDLE_INIT(hkey); /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ @@ -1596,6 +2380,16 @@ static struct ibm_struct bluetooth_driver_data = { * Wan subdriver */ +enum { + /* ACPI GWAN/SWAN bits */ + TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ + TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ + TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ +}; + +static int wan_get_radiosw(void); +static int wan_set_radiosw(int radio_on); + /* sysfs wan enable ---------------------------------------------------- */ static ssize_t wan_enable_show(struct device *dev, struct device_attribute *attr, @@ -1647,7 +2441,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n"); - IBM_ACPIHANDLE_INIT(hkey); + TPACPI_ACPIHANDLE_INIT(hkey); tp_features.wan = hkey_handle && acpi_evalf(hkey_handle, &status, "GWAN", "qd"); @@ -1759,17 +2553,41 @@ static struct ibm_struct wan_driver_data = { * Video subdriver */ +enum video_access_mode { + TPACPI_VIDEO_NONE = 0, + TPACPI_VIDEO_570, /* 570 */ + TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */ + TPACPI_VIDEO_NEW, /* all others */ +}; + +enum { /* video status flags, based on VIDEO_570 */ + TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */ + TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */ + TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */ +}; + +enum { /* TPACPI_VIDEO_570 constants */ + TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to + * video_status_flags */ + TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */ + TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */ +}; + static enum video_access_mode video_supported; static int video_orig_autosw; -IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */ +static int video_autosw_get(void); +static int video_autosw_set(int enable); + +TPACPI_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 */ +TPACPI_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */ static int __init video_init(struct ibm_init_struct *iibm) { @@ -1777,8 +2595,8 @@ static int __init video_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n"); - IBM_ACPIHANDLE_INIT(vid); - IBM_ACPIHANDLE_INIT(vid2); + TPACPI_ACPIHANDLE_INIT(vid); + TPACPI_ACPIHANDLE_INIT(vid2); if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga) /* G41, assume IVGA doesn't change */ @@ -1809,7 +2627,7 @@ static void video_exit(void) dbg_printk(TPACPI_DBG_EXIT, "restoring original video autoswitch mode\n"); if (video_autosw_set(video_orig_autosw)) - printk(IBM_ERR "error while trying to restore original " + printk(TPACPI_ERR "error while trying to restore original " "video autoswitch mode\n"); } @@ -1882,13 +2700,14 @@ static int video_outputsw_set(int status) res = acpi_evalf(vid_handle, NULL, "ASWT", "vdd", status * 0x100, 0); if (!autosw && video_autosw_set(autosw)) { - printk(IBM_ERR "video auto-switch left enabled due to error\n"); + printk(TPACPI_ERR + "video auto-switch left enabled due to error\n"); return -EIO; } break; case TPACPI_VIDEO_NEW: res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) && - acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); + acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1); break; default: return -ENOSYS; @@ -1951,7 +2770,8 @@ static int video_outputsw_cycle(void) return -ENOSYS; } if (!autosw && video_autosw_set(autosw)) { - printk(IBM_ERR "video auto-switch left enabled due to error\n"); + printk(TPACPI_ERR + "video auto-switch left enabled due to error\n"); return -EIO; } @@ -2080,16 +2900,16 @@ static struct ibm_struct video_driver_data = { * Light (thinklight) subdriver */ -IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ -IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */ +TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ +TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */ static int __init light_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); - IBM_ACPIHANDLE_INIT(ledb); - IBM_ACPIHANDLE_INIT(lght); - IBM_ACPIHANDLE_INIT(cmos); + TPACPI_ACPIHANDLE_INIT(ledb); + TPACPI_ACPIHANDLE_INIT(lght); + TPACPI_ACPIHANDLE_INIT(cmos); /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */ tp_features.light = (cmos_handle || lght_handle) && !ledb_handle; @@ -2167,14 +2987,18 @@ static struct ibm_struct light_driver_data = { #ifdef CONFIG_THINKPAD_ACPI_DOCK -IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */ +static void dock_notify(struct ibm_struct *ibm, u32 event); +static int dock_read(char *p); +static int dock_write(char *buf); + +TPACPI_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 */ /* don't list other alternatives as we install a notify handler on the 570 */ -IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ +TPACPI_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */ static const struct acpi_device_id ibm_pci_device_ids[] = { {PCI_ROOT_HID_STRING, 0}, @@ -2217,7 +3041,7 @@ static int __init dock_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n"); - IBM_ACPIHANDLE_INIT(dock); + TPACPI_ACPIHANDLE_INIT(dock); vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n", str_supported(dock_handle != NULL)); @@ -2233,7 +3057,7 @@ static int __init dock_init2(struct ibm_init_struct *iibm) if (dock_driver_data[0].flags.acpi_driver_registered && dock_driver_data[0].flags.acpi_notify_installed) { - IBM_ACPIHANDLE_INIT(pci); + TPACPI_ACPIHANDLE_INIT(pci); dock2_needed = (pci_handle != NULL); vdbg_printk(TPACPI_DBG_INIT, "dock PCI handler for the TP 570 is %s\n", @@ -2265,7 +3089,7 @@ static void dock_notify(struct ibm_struct *ibm, u32 event) else if (event == 0 && docked) data = 3; /* dock */ else { - printk(IBM_ERR "unknown dock event %d, status %d\n", + printk(TPACPI_ERR "unknown dock event %d, status %d\n", event, _sta(dock_handle)); data = 0; /* unknown */ } @@ -2321,18 +3145,19 @@ static int dock_write(char *buf) */ #ifdef CONFIG_THINKPAD_ACPI_BAY -IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */ + +TPACPI_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 */ +TPACPI_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 */ +TPACPI_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 */ +TPACPI_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */ "_EJ0", /* 770x */ ); /* all others */ @@ -2340,12 +3165,12 @@ static int __init bay_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n"); - IBM_ACPIHANDLE_INIT(bay); + TPACPI_ACPIHANDLE_INIT(bay); if (bay_handle) - IBM_ACPIHANDLE_INIT(bay_ej); - IBM_ACPIHANDLE_INIT(bay2); + TPACPI_ACPIHANDLE_INIT(bay_ej); + TPACPI_ACPIHANDLE_INIT(bay2); if (bay2_handle) - IBM_ACPIHANDLE_INIT(bay2_ej); + TPACPI_ACPIHANDLE_INIT(bay2_ej); tp_features.bay_status = bay_handle && acpi_evalf(bay_handle, NULL, "_STA", "qv"); @@ -2474,7 +3299,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT, "initializing cmos commands subdriver\n"); - IBM_ACPIHANDLE_INIT(cmos); + TPACPI_ACPIHANDLE_INIT(cmos); vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", str_supported(cmos_handle != NULL)); @@ -2538,10 +3363,24 @@ static struct ibm_struct cmos_driver_data = { * LED subdriver */ +enum led_access_mode { + TPACPI_LED_NONE = 0, + TPACPI_LED_570, /* 570 */ + TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ + TPACPI_LED_NEW, /* all others */ +}; + +enum { /* For TPACPI_LED_OLD */ + TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ + TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ + TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ +}; + 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 */ +TPACPI_HANDLE(led, ec, "SLED", /* 570 */ + "SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, */ + /* T20-22, X20-21 */ "LED", /* all others */ ); /* R30, R31 */ @@ -2549,7 +3388,7 @@ static int __init led_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); - IBM_ACPIHANDLE_INIT(led); + TPACPI_ACPIHANDLE_INIT(led); if (!led_handle) /* led not supported on R30, R31 */ @@ -2638,13 +3477,11 @@ static int led_write(char *buf) led = 1 << led; ret = ec_write(TPACPI_LED_EC_HLMS, led); if (ret >= 0) - ret = - ec_write(TPACPI_LED_EC_HLBL, - led * led_exp_hlbl[ind]); + ret = ec_write(TPACPI_LED_EC_HLBL, + led * led_exp_hlbl[ind]); if (ret >= 0) - ret = - ec_write(TPACPI_LED_EC_HLCL, - led * led_exp_hlcl[ind]); + ret = ec_write(TPACPI_LED_EC_HLCL, + led * led_exp_hlcl[ind]); if (ret < 0) return ret; } else { @@ -2668,13 +3505,13 @@ static struct ibm_struct led_driver_data = { * Beep subdriver */ -IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ +TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */ static int __init beep_init(struct ibm_init_struct *iibm) { vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n"); - IBM_ACPIHANDLE_INIT(beep); + TPACPI_ACPIHANDLE_INIT(beep); vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", str_supported(beep_handle != NULL)); @@ -2727,8 +3564,109 @@ static struct ibm_struct beep_driver_data = { * Thermal subdriver */ +enum thermal_access_mode { + TPACPI_THERMAL_NONE = 0, /* No thermal support */ + TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ + TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ + TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ + TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ +}; + +enum { /* TPACPI_THERMAL_TPEC_* */ + TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ + TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ + TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ +}; + +#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ +struct ibm_thermal_sensors_struct { + s32 temp[TPACPI_MAX_THERMAL_SENSORS]; +}; + static enum thermal_access_mode thermal_read_mode; +/* idx is zero-based */ +static int thermal_get_sensor(int idx, s32 *value) +{ + int t; + s8 tmp; + char tmpi[5]; + + t = TP_EC_THERMAL_TMP0; + + switch (thermal_read_mode) { +#if TPACPI_MAX_THERMAL_SENSORS >= 16 + case TPACPI_THERMAL_TPEC_16: + if (idx >= 8 && idx <= 15) { + t = TP_EC_THERMAL_TMP8; + idx -= 8; + } + /* fallthrough */ +#endif + case TPACPI_THERMAL_TPEC_8: + if (idx <= 7) { + if (!acpi_ec_read(t + idx, &tmp)) + return -EIO; + *value = tmp * 1000; + return 0; + } + break; + + case TPACPI_THERMAL_ACPI_UPDT: + if (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); + if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) + return -EIO; + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + *value = (t - 2732) * 100; + return 0; + } + break; + + case TPACPI_THERMAL_ACPI_TMP07: + if (idx <= 7) { + snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); + if (!acpi_evalf(ec_handle, &t, tmpi, "d")) + return -EIO; + if (t > 127 || t < -127) + t = TP_EC_THERMAL_TMP_NA; + *value = t * 1000; + return 0; + } + break; + + case TPACPI_THERMAL_NONE: + default: + return -ENOSYS; + } + + return -EINVAL; +} + +static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) +{ + int res, i; + int n; + + n = 8; + i = 0; + + if (!s) + return -EINVAL; + + if (thermal_read_mode == TPACPI_THERMAL_TPEC_16) + n = 16; + + for (i = 0 ; i < n; i++) { + res = thermal_get_sensor(i, &s->temp[i]); + if (res) + return res; + } + + return n; +} + /* sysfs temp##_input -------------------------------------------------- */ static ssize_t thermal_temp_input_show(struct device *dev, @@ -2751,7 +3689,8 @@ static ssize_t thermal_temp_input_show(struct device *dev, } #define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \ - SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB) + SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, \ + thermal_temp_input_show, NULL, _idxB) static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = { THERMAL_SENSOR_ATTR_TEMP(1, 0), @@ -2845,12 +3784,13 @@ static int __init thermal_init(struct ibm_init_struct *iibm) if (ta1 == 0) { /* This is sheer paranoia, but we handle it anyway */ if (acpi_tmp7) { - printk(IBM_ERR + printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " - "falling back to ACPI TMPx access mode\n"); + "falling back to ACPI TMPx access " + "mode\n"); thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07; } else { - printk(IBM_ERR + printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " "disabling thermal sensors access\n"); thermal_read_mode = TPACPI_THERMAL_NONE; @@ -2877,7 +3817,7 @@ static int __init thermal_init(struct ibm_init_struct *iibm) str_supported(thermal_read_mode != TPACPI_THERMAL_NONE), thermal_read_mode); - switch(thermal_read_mode) { + switch (thermal_read_mode) { case TPACPI_THERMAL_TPEC_16: res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, &thermal_temp_input16_group); @@ -2902,7 +3842,7 @@ static int __init thermal_init(struct ibm_init_struct *iibm) static void thermal_exit(void) { - switch(thermal_read_mode) { + switch (thermal_read_mode) { case TPACPI_THERMAL_TPEC_16: sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &thermal_temp_input16_group); @@ -2919,88 +3859,6 @@ static void thermal_exit(void) } } -/* idx is zero-based */ -static int thermal_get_sensor(int idx, s32 *value) -{ - int t; - s8 tmp; - char tmpi[5]; - - t = TP_EC_THERMAL_TMP0; - - switch (thermal_read_mode) { -#if TPACPI_MAX_THERMAL_SENSORS >= 16 - case TPACPI_THERMAL_TPEC_16: - if (idx >= 8 && idx <= 15) { - t = TP_EC_THERMAL_TMP8; - idx -= 8; - } - /* fallthrough */ -#endif - case TPACPI_THERMAL_TPEC_8: - if (idx <= 7) { - if (!acpi_ec_read(t + idx, &tmp)) - return -EIO; - *value = tmp * 1000; - return 0; - } - break; - - case TPACPI_THERMAL_ACPI_UPDT: - if (idx <= 7) { - snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); - if (!acpi_evalf(ec_handle, NULL, "UPDT", "v")) - return -EIO; - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - *value = (t - 2732) * 100; - return 0; - } - break; - - case TPACPI_THERMAL_ACPI_TMP07: - if (idx <= 7) { - snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx); - if (!acpi_evalf(ec_handle, &t, tmpi, "d")) - return -EIO; - if (t > 127 || t < -127) - t = TP_EC_THERMAL_TMP_NA; - *value = t * 1000; - return 0; - } - break; - - case TPACPI_THERMAL_NONE: - default: - return -ENOSYS; - } - - return -EINVAL; -} - -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) -{ - int res, i; - int n; - - n = 8; - i = 0; - - if (!s) - return -EINVAL; - - if (thermal_read_mode == TPACPI_THERMAL_TPEC_16) - n = 16; - - for(i = 0 ; i < n; i++) { - res = thermal_get_sensor(i, &s->temp[i]); - if (res) - return res; - } - - return n; -} - static int thermal_read(char *p) { int len = 0; @@ -3103,14 +3961,110 @@ static struct ibm_struct ecdump_driver_data = { * Backlight/brightness subdriver */ +#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen" + static struct backlight_device *ibm_backlight_device; +static int brightness_offset = 0x31; +static int brightness_mode; +static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */ + +static struct mutex brightness_mutex; + +/* + * ThinkPads can read brightness from two places: EC 0x31, or + * CMOS NVRAM byte 0x5E, bits 0-3. + */ +static int brightness_get(struct backlight_device *bd) +{ + u8 lec = 0, lcmos = 0, level = 0; + + if (brightness_mode & 1) { + if (!acpi_ec_read(brightness_offset, &lec)) + return -EIO; + lec &= (tp_features.bright_16levels)? 0x0f : 0x07; + level = lec; + }; + if (brightness_mode & 2) { + lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS) + & TP_NVRAM_MASK_LEVEL_BRIGHTNESS) + >> TP_NVRAM_POS_LEVEL_BRIGHTNESS; + lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07; + level = lcmos; + } + + if (brightness_mode == 3 && lec != lcmos) { + printk(TPACPI_ERR + "CMOS NVRAM (%u) and EC (%u) do not agree " + "on display brightness level\n", + (unsigned int) lcmos, + (unsigned int) lec); + return -EIO; + } + + return level; +} + +/* May return EINTR which can always be mapped to ERESTARTSYS */ +static int brightness_set(int value) +{ + int cmos_cmd, inc, i, res; + int current_value; + + if (value > ((tp_features.bright_16levels)? 15 : 7)) + return -EINVAL; + + res = mutex_lock_interruptible(&brightness_mutex); + if (res < 0) + return res; + + current_value = brightness_get(NULL); + if (current_value < 0) { + res = current_value; + goto errout; + } + + cmos_cmd = value > current_value ? + TP_CMOS_BRIGHTNESS_UP : + TP_CMOS_BRIGHTNESS_DOWN; + inc = (value > current_value)? 1 : -1; + + res = 0; + for (i = current_value; i != value; i += inc) { + if ((brightness_mode & 2) && + issue_thinkpad_cmos_command(cmos_cmd)) { + res = -EIO; + goto errout; + } + if ((brightness_mode & 1) && + !acpi_ec_write(brightness_offset, i + inc)) { + res = -EIO; + goto errout;; + } + } + +errout: + mutex_unlock(&brightness_mutex); + return res; +} + +/* sysfs backlight class ----------------------------------------------- */ + +static int brightness_update_status(struct backlight_device *bd) +{ + /* it is the backlight class's job (caller) to handle + * EINTR and other errors properly */ + 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, + .get_brightness = brightness_get, + .update_status = brightness_update_status, }; -static struct mutex brightness_mutex; +/* --------------------------------------------------------------------- */ static int __init tpacpi_query_bcll_levels(acpi_handle handle) { @@ -3121,8 +4075,8 @@ static int __init tpacpi_query_bcll_levels(acpi_handle handle) if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) { obj = (union acpi_object *)buffer.pointer; if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { - printk(IBM_ERR "Unknown BCLL data, " - "please report this to %s\n", IBM_MAIL); + printk(TPACPI_ERR "Unknown BCLL data, " + "please report this to %s\n", TPACPI_MAIL); rc = 0; } else { rc = obj->package.count; @@ -3160,14 +4114,15 @@ static int __init brightness_check_levels(void) void *found_node = NULL; if (!vid_handle) { - IBM_ACPIHANDLE_INIT(vid); + TPACPI_ACPIHANDLE_INIT(vid); } if (!vid_handle) return 0; /* Search for a BCLL package with 16 levels */ status = acpi_walk_namespace(ACPI_TYPE_PACKAGE, vid_handle, 3, - brightness_find_bcll, NULL, &found_node); + brightness_find_bcll, NULL, + &found_node); return (ACPI_SUCCESS(status) && found_node != NULL); } @@ -3193,7 +4148,7 @@ static int __init brightness_check_std_acpi_support(void) void *found_node = NULL; if (!vid_handle) { - IBM_ACPIHANDLE_INIT(vid); + TPACPI_ACPIHANDLE_INIT(vid); } if (!vid_handle) return 0; @@ -3219,8 +4174,9 @@ static int __init brightness_init(struct ibm_init_struct *iibm) return 1; } else if (brightness_enable > 1) { if (brightness_check_std_acpi_support()) { - printk(IBM_NOTICE - "standard ACPI backlight interface available, not loading native one...\n"); + printk(TPACPI_NOTICE + "standard ACPI backlight interface " + "available, not loading native one...\n"); return 1; } } @@ -3247,13 +4203,14 @@ static int __init brightness_init(struct ibm_init_struct *iibm) return 1; if (tp_features.bright_16levels) - printk(IBM_INFO "detected a 16-level brightness capable ThinkPad\n"); + printk(TPACPI_INFO + "detected a 16-level brightness capable ThinkPad\n"); ibm_backlight_device = backlight_device_register( TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL, &ibm_backlight_data); if (IS_ERR(ibm_backlight_device)) { - printk(IBM_ERR "Could not register backlight device\n"); + printk(TPACPI_ERR "Could not register backlight device\n"); return PTR_ERR(ibm_backlight_device); } vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n"); @@ -3276,99 +4233,13 @@ static void brightness_exit(void) } } -static int brightness_update_status(struct backlight_device *bd) -{ - /* it is the backlight class's job (caller) to handle - * EINTR and other errors properly */ - return brightness_set( - (bd->props.fb_blank == FB_BLANK_UNBLANK && - bd->props.power == FB_BLANK_UNBLANK) ? - bd->props.brightness : 0); -} - -/* - * ThinkPads can read brightness from two places: EC 0x31, or - * CMOS NVRAM byte 0x5E, bits 0-3. - */ -static int brightness_get(struct backlight_device *bd) -{ - u8 lec = 0, lcmos = 0, level = 0; - - if (brightness_mode & 1) { - if (!acpi_ec_read(brightness_offset, &lec)) - return -EIO; - lec &= (tp_features.bright_16levels)? 0x0f : 0x07; - level = lec; - }; - if (brightness_mode & 2) { - lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS) - & TP_NVRAM_MASK_LEVEL_BRIGHTNESS) - >> TP_NVRAM_POS_LEVEL_BRIGHTNESS; - lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07; - level = lcmos; - } - - if (brightness_mode == 3 && lec != lcmos) { - printk(IBM_ERR - "CMOS NVRAM (%u) and EC (%u) do not agree " - "on display brightness level\n", - (unsigned int) lcmos, - (unsigned int) lec); - return -EIO; - } - - return level; -} - -/* May return EINTR which can always be mapped to ERESTARTSYS */ -static int brightness_set(int value) -{ - int cmos_cmd, inc, i, res; - int current_value; - - if (value > ((tp_features.bright_16levels)? 15 : 7)) - return -EINVAL; - - res = mutex_lock_interruptible(&brightness_mutex); - if (res < 0) - return res; - - current_value = brightness_get(NULL); - if (current_value < 0) { - res = current_value; - goto errout; - } - - cmos_cmd = value > current_value ? - TP_CMOS_BRIGHTNESS_UP : - TP_CMOS_BRIGHTNESS_DOWN; - inc = (value > current_value)? 1 : -1; - - res = 0; - for (i = current_value; i != value; i += inc) { - if ((brightness_mode & 2) && - issue_thinkpad_cmos_command(cmos_cmd)) { - res = -EIO; - goto errout; - } - if ((brightness_mode & 1) && - !acpi_ec_write(brightness_offset, i + inc)) { - res = -EIO; - goto errout;; - } - } - -errout: - mutex_unlock(&brightness_mutex); - return res; -} - static int brightness_read(char *p) { int len = 0; int level; - if ((level = brightness_get(NULL)) < 0) { + level = brightness_get(NULL); + if (level < 0) { len += sprintf(p + len, "level:\t\tunreadable\n"); } else { len += sprintf(p + len, "level:\t\t%d\n", level); @@ -3425,6 +4296,8 @@ static struct ibm_struct brightness_driver_data = { * Volume subdriver */ +static int volume_offset = 0x30; + static int volume_read(char *p) { int len = 0; @@ -3475,7 +4348,8 @@ static int volume_write(char *buf) return -EINVAL; if (new_level != level) { /* mute doesn't change */ - cmos_cmd = new_level > level ? TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; + cmos_cmd = (new_level > level) ? + TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; inc = new_level > level ? 1 : -1; if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || @@ -3487,14 +4361,16 @@ static int volume_write(char *buf) !acpi_ec_write(volume_offset, i + inc)) return -EIO; - if (mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) || - !acpi_ec_write(volume_offset, - new_level + mute))) + if (mute && + (issue_thinkpad_cmos_command(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; + cmos_cmd = (new_mute) ? + TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; if (issue_thinkpad_cmos_command(cmos_cmd) || !acpi_ec_write(volume_offset, level + new_mute)) @@ -3616,26 +4492,377 @@ static struct ibm_struct volume_driver_data = { * but the ACPI tables just mention level 7. */ +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 */ + + TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ + TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ + + TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ +}; + +enum fan_status_access_mode { + TPACPI_FAN_NONE = 0, /* No fan status or control */ + TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ + TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ +}; + +enum fan_control_access_mode { + TPACPI_FAN_WR_NONE = 0, /* No fan control */ + TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ + TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ + TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ +}; + +enum fan_control_commands { + TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ + TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ + TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, + * and also watchdog cmd */ +}; + +static int fan_control_allowed; + 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 u8 fan_control_initial_status; static u8 fan_control_desired_level; +static int fan_watchdog_maxinterval; + +static struct mutex fan_mutex; 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 */ +TPACPI_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ +TPACPI_HANDLE(gfan, ec, "GFAN", /* 570 */ "\\FSPD", /* 600e/x, 770e, 770x */ ); /* all others */ -IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ +TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ /* + * Call with fan_mutex held + */ +static void fan_update_desired_level(u8 status) +{ + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + if (status > 7) + fan_control_desired_level = 7; + else + fan_control_desired_level = status; + } +} + +static int fan_get_status(u8 *status) +{ + u8 s; + + /* TODO: + * Add TPACPI_FAN_RD_ACPI_FANS ? */ + + switch (fan_status_access_mode) { + case TPACPI_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 TPACPI_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_status_safe(u8 *status) +{ + int rc; + u8 s; + + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + rc = fan_get_status(&s); + if (!rc) + fan_update_desired_level(s); + mutex_unlock(&fan_mutex); + + if (status) + *status = s; + + return rc; +} + +static int fan_get_speed(unsigned int *speed) +{ + u8 hi, lo; + + switch (fan_status_access_mode) { + case TPACPI_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 int fan_set_level(int level) +{ + if (!fan_control_allowed) + return -EPERM; + + switch (fan_control_access_mode) { + case TPACPI_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 TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + if ((level != TP_EC_FAN_AUTO) && + (level != TP_EC_FAN_FULLSPEED) && + ((level < 0) || (level > 7))) + return -EINVAL; + + /* safety net should the EC not support AUTO + * or FULLSPEED mode bits and just ignore them */ + if (level & TP_EC_FAN_FULLSPEED) + level |= 7; /* safety min speed 7 */ + else if (level & TP_EC_FAN_FULLSPEED) + level |= 4; /* safety min speed 4 */ + + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + else + tp_features.fan_ctrl_status_undef = 0; + break; + + default: + return -ENXIO; + } + return 0; +} + +static int fan_set_level_safe(int level) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + + if (level == TPACPI_FAN_LAST_LEVEL) + level = fan_control_desired_level; + + rc = fan_set_level(level); + if (!rc) + fan_update_desired_level(level); + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_enable(void) +{ + u8 s; + int rc; + + if (!fan_control_allowed) + return -EPERM; + + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + rc = fan_get_status(&s); + if (rc < 0) + break; + + /* Don't go out of emergency fan mode */ + if (s != 7) { + s &= 0x07; + s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ + } + + if (!acpi_ec_write(fan_status_offset, s)) + rc = -EIO; + else { + tp_features.fan_ctrl_status_undef = 0; + rc = 0; + } + break; + + case TPACPI_FAN_WR_ACPI_SFAN: + rc = fan_get_status(&s); + if (rc < 0) + break; + + s &= 0x07; + + /* Set fan to at least level 4 */ + s |= 4; + + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) + rc = -EIO; + else + rc = 0; + break; + + default: + rc = -ENXIO; + } + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_disable(void) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + + rc = 0; + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + case TPACPI_FAN_WR_TPEC: + if (!acpi_ec_write(fan_status_offset, 0x00)) + rc = -EIO; + else { + fan_control_desired_level = 0; + tp_features.fan_ctrl_status_undef = 0; + } + break; + + case TPACPI_FAN_WR_ACPI_SFAN: + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) + rc = -EIO; + else + fan_control_desired_level = 0; + break; + + default: + rc = -ENXIO; + } + + + mutex_unlock(&fan_mutex); + return rc; +} + +static int fan_set_speed(int speed) +{ + int rc; + + if (!fan_control_allowed) + return -EPERM; + + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + + rc = 0; + switch (fan_control_access_mode) { + case TPACPI_FAN_WR_ACPI_FANS: + if (speed >= 0 && speed <= 65535) { + if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", + speed, speed, speed)) + rc = -EIO; + } else + rc = -EINVAL; + break; + + default: + rc = -ENXIO; + } + + mutex_unlock(&fan_mutex); + return rc; +} + +static void fan_watchdog_reset(void) +{ + static int fan_watchdog_active; + + if (fan_control_access_mode == TPACPI_FAN_WR_NONE) + return; + + if (fan_watchdog_active) + cancel_delayed_work(&fan_watchdog_task); + + if (fan_watchdog_maxinterval > 0 && + tpacpi_lifecycle != TPACPI_LIFE_EXITING) { + fan_watchdog_active = 1; + if (!schedule_delayed_work(&fan_watchdog_task, + msecs_to_jiffies(fan_watchdog_maxinterval + * 1000))) { + printk(TPACPI_ERR + "failed to schedule the fan watchdog, " + "watchdog will not trigger\n"); + } + } else + fan_watchdog_active = 0; +} + +static void fan_watchdog_fire(struct work_struct *ignored) +{ + int rc; + + if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING) + return; + + printk(TPACPI_NOTICE "fan watchdog: enabling fan\n"); + rc = fan_set_enable(); + if (rc < 0) { + printk(TPACPI_ERR "fan watchdog: error %d while enabling fan, " + "will try again later...\n", -rc); + /* reschedule for later */ + fan_watchdog_reset(); + } +} + +/* * SYSFS fan layout: hwmon compatible (device) * * pwm*_enable: @@ -3868,9 +5095,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) tp_features.fan_ctrl_status_undef = 0; fan_control_desired_level = 7; - IBM_ACPIHANDLE_INIT(fans); - IBM_ACPIHANDLE_INIT(gfan); - IBM_ACPIHANDLE_INIT(sfan); + TPACPI_ACPIHANDLE_INIT(fans); + TPACPI_ACPIHANDLE_INIT(gfan); + TPACPI_ACPIHANDLE_INIT(sfan); if (gfan_handle) { /* 570, 600e/x, 770e, 770x */ @@ -3896,16 +5123,16 @@ static int __init fan_init(struct ibm_init_struct *iibm) case 0x3837: /* TP-78 */ case 0x3637: /* TP-76 */ case 0x3037: /* TP-70 */ - printk(IBM_NOTICE - "fan_init: initial fan status is " - "unknown, assuming it is in auto " - "mode\n"); + printk(TPACPI_NOTICE + "fan_init: initial fan status " + "is unknown, assuming it is " + "in auto mode\n"); tp_features.fan_ctrl_status_undef = 1; ;; } } } else { - printk(IBM_ERR + printk(TPACPI_ERR "ThinkPad ACPI EC access misbehaving, " "fan status and control unavailable\n"); return 1; @@ -3970,333 +5197,20 @@ static int __init fan_init(struct ibm_init_struct *iibm) return 1; } -/* - * Call with fan_mutex held - */ -static void fan_update_desired_level(u8 status) -{ - if ((status & - (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { - if (status > 7) - fan_control_desired_level = 7; - else - fan_control_desired_level = status; - } -} - -static int fan_get_status(u8 *status) -{ - u8 s; - - /* TODO: - * Add TPACPI_FAN_RD_ACPI_FANS ? */ - - switch (fan_status_access_mode) { - case TPACPI_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 TPACPI_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_status_safe(u8 *status) -{ - int rc; - u8 s; - - if (mutex_lock_interruptible(&fan_mutex)) - return -ERESTARTSYS; - rc = fan_get_status(&s); - if (!rc) - fan_update_desired_level(s); - mutex_unlock(&fan_mutex); - - if (status) - *status = s; - - return rc; -} - static void fan_exit(void) { - vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n"); + vdbg_printk(TPACPI_DBG_EXIT, + "cancelling any pending fan watchdog tasks\n"); /* FIXME: can we really do this unconditionally? */ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group); - driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog); + driver_remove_file(&tpacpi_hwmon_pdriver.driver, + &driver_attr_fan_watchdog); 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 TPACPI_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) -{ - int rc; - - if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING) - return; - - printk(IBM_NOTICE "fan watchdog: enabling fan\n"); - rc = fan_set_enable(); - if (rc < 0) { - printk(IBM_ERR "fan watchdog: error %d while enabling fan, " - "will try again later...\n", -rc); - /* reschedule for later */ - fan_watchdog_reset(); - } -} - -static void fan_watchdog_reset(void) -{ - static int fan_watchdog_active; - - if (fan_control_access_mode == TPACPI_FAN_WR_NONE) - return; - - if (fan_watchdog_active) - cancel_delayed_work(&fan_watchdog_task); - - if (fan_watchdog_maxinterval > 0 && - tpacpi_lifecycle != TPACPI_LIFE_EXITING) { - 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) -{ - if (!fan_control_allowed) - return -EPERM; - - switch (fan_control_access_mode) { - case TPACPI_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 TPACPI_FAN_WR_ACPI_FANS: - case TPACPI_FAN_WR_TPEC: - if ((level != TP_EC_FAN_AUTO) && - (level != TP_EC_FAN_FULLSPEED) && - ((level < 0) || (level > 7))) - return -EINVAL; - - /* safety net should the EC not support AUTO - * or FULLSPEED mode bits and just ignore them */ - if (level & TP_EC_FAN_FULLSPEED) - level |= 7; /* safety min speed 7 */ - else if (level & TP_EC_FAN_FULLSPEED) - level |= 4; /* safety min speed 4 */ - - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - else - tp_features.fan_ctrl_status_undef = 0; - break; - - default: - return -ENXIO; - } - return 0; -} - -static int fan_set_level_safe(int level) -{ - int rc; - - if (!fan_control_allowed) - return -EPERM; - - if (mutex_lock_interruptible(&fan_mutex)) - return -ERESTARTSYS; - - if (level == TPACPI_FAN_LAST_LEVEL) - level = fan_control_desired_level; - - rc = fan_set_level(level); - if (!rc) - fan_update_desired_level(level); - - mutex_unlock(&fan_mutex); - return rc; -} - -static int fan_set_enable(void) -{ - u8 s; - int rc; - - if (!fan_control_allowed) - return -EPERM; - - if (mutex_lock_interruptible(&fan_mutex)) - return -ERESTARTSYS; - - switch (fan_control_access_mode) { - case TPACPI_FAN_WR_ACPI_FANS: - case TPACPI_FAN_WR_TPEC: - rc = fan_get_status(&s); - if (rc < 0) - break; - - /* Don't go out of emergency fan mode */ - if (s != 7) { - s &= 0x07; - s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ - } - - if (!acpi_ec_write(fan_status_offset, s)) - rc = -EIO; - else { - tp_features.fan_ctrl_status_undef = 0; - rc = 0; - } - break; - - case TPACPI_FAN_WR_ACPI_SFAN: - rc = fan_get_status(&s); - if (rc < 0) - break; - - s &= 0x07; - - /* Set fan to at least level 4 */ - s |= 4; - - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) - rc= -EIO; - else - rc = 0; - break; - - default: - rc = -ENXIO; - } - - mutex_unlock(&fan_mutex); - return rc; -} - -static int fan_set_disable(void) -{ - int rc; - - if (!fan_control_allowed) - return -EPERM; - - if (mutex_lock_interruptible(&fan_mutex)) - return -ERESTARTSYS; - - rc = 0; - switch (fan_control_access_mode) { - case TPACPI_FAN_WR_ACPI_FANS: - case TPACPI_FAN_WR_TPEC: - if (!acpi_ec_write(fan_status_offset, 0x00)) - rc = -EIO; - else { - fan_control_desired_level = 0; - tp_features.fan_ctrl_status_undef = 0; - } - break; - - case TPACPI_FAN_WR_ACPI_SFAN: - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00)) - rc = -EIO; - else - fan_control_desired_level = 0; - break; - - default: - rc = -ENXIO; - } - - - mutex_unlock(&fan_mutex); - return rc; -} - -static int fan_set_speed(int speed) -{ - int rc; - - if (!fan_control_allowed) - return -EPERM; - - if (mutex_lock_interruptible(&fan_mutex)) - return -ERESTARTSYS; - - rc = 0; - switch (fan_control_access_mode) { - case TPACPI_FAN_WR_ACPI_FANS: - if (speed >= 0 && speed <= 65535) { - if (!acpi_evalf(fans_handle, NULL, NULL, "vddd", - speed, speed, speed)) - rc = -EIO; - } else - rc = -EINVAL; - break; - - default: - rc = -ENXIO; - } - - mutex_unlock(&fan_mutex); - return rc; -} - static int fan_read(char *p) { int len = 0; @@ -4307,7 +5221,8 @@ static int fan_read(char *p) switch (fan_status_access_mode) { case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status_safe(&status)) < 0) + rc = fan_get_status_safe(&status); + if (rc < 0) return rc; len += sprintf(p + len, "status:\t\t%s\n" @@ -4317,7 +5232,8 @@ static int fan_read(char *p) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ - if ((rc = fan_get_status_safe(&status)) < 0) + rc = fan_get_status_safe(&status); + if (rc < 0) return rc; if (unlikely(tp_features.fan_ctrl_status_undef)) { @@ -4332,7 +5248,8 @@ static int fan_read(char *p) len += sprintf(p + len, "status:\t\t%s\n", (status != 0) ? "enabled" : "disabled"); - if ((rc = fan_get_speed(&speed)) < 0) + rc = fan_get_speed(&speed); + if (rc < 0) return rc; len += sprintf(p + len, "speed:\t\t%d\n", speed); @@ -4368,8 +5285,8 @@ static int fan_read(char *p) if (fan_control_commands & TPACPI_FAN_CMD_ENABLE) len += sprintf(p + len, "commands:\tenable, disable\n" - "commands:\twatchdog ( is 0 (off), " - "1-120 (seconds))\n"); + "commands:\twatchdog ( " + "is 0 (off), 1-120 (seconds))\n"); if (fan_control_commands & TPACPI_FAN_CMD_SPEED) len += sprintf(p + len, "commands:\tspeed " @@ -4390,8 +5307,9 @@ static int fan_write_cmd_level(const char *cmd, int *rc) else if (sscanf(cmd, "level %d", &level) != 1) return 0; - if ((*rc = fan_set_level_safe(level)) == -ENXIO) - printk(IBM_ERR "level command accepted for unsupported " + *rc = fan_set_level_safe(level); + if (*rc == -ENXIO) + printk(TPACPI_ERR "level command accepted for unsupported " "access mode %d", fan_control_access_mode); return 1; @@ -4402,8 +5320,9 @@ 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 " + *rc = fan_set_enable(); + if (*rc == -ENXIO) + printk(TPACPI_ERR "enable command accepted for unsupported " "access mode %d", fan_control_access_mode); return 1; @@ -4414,8 +5333,9 @@ 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 " + *rc = fan_set_disable(); + if (*rc == -ENXIO) + printk(TPACPI_ERR "disable command accepted for unsupported " "access mode %d", fan_control_access_mode); return 1; @@ -4431,8 +5351,9 @@ static int fan_write_cmd_speed(const char *cmd, int *rc) if (sscanf(cmd, "speed %d", &speed) != 1) return 0; - if ((*rc = fan_set_speed(speed)) == -ENXIO) - printk(IBM_ERR "speed command accepted for unsupported " + *rc = fan_set_speed(speed); + if (*rc == -ENXIO) + printk(TPACPI_ERR "speed command accepted for unsupported " "access mode %d", fan_control_access_mode); return 1; @@ -4496,7 +5417,7 @@ static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev, struct device_attribute *attr, char *buf) { - return snprintf(buf, PAGE_SIZE, "%s\n", IBM_NAME); + return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME); } static struct device_attribute dev_attr_thinkpad_acpi_pdev_name = @@ -4507,14 +5428,12 @@ static struct device_attribute dev_attr_thinkpad_acpi_pdev_name = /* /proc support */ static struct proc_dir_entry *proc_dir; -/* Subdriver registry */ -static LIST_HEAD(tpacpi_all_drivers); - - /* * Module and infrastructure proble, init and exit handling */ +static int force_load; + #ifdef CONFIG_THINKPAD_ACPI_DEBUG static const char * __init str_supported(int is_supported) { @@ -4524,6 +5443,48 @@ static const char * __init str_supported(int is_supported) } #endif /* CONFIG_THINKPAD_ACPI_DEBUG */ +static void ibm_exit(struct ibm_struct *ibm) +{ + dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name); + + list_del_init(&ibm->all_drivers); + + if (ibm->flags.acpi_notify_installed) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: acpi_remove_notify_handler\n", ibm->name); + BUG_ON(!ibm->acpi); + acpi_remove_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, + dispatch_acpi_notify); + ibm->flags.acpi_notify_installed = 0; + ibm->flags.acpi_notify_installed = 0; + } + + if (ibm->flags.proc_created) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: remove_proc_entry\n", ibm->name); + remove_proc_entry(ibm->name, proc_dir); + ibm->flags.proc_created = 0; + } + + if (ibm->flags.acpi_driver_registered) { + dbg_printk(TPACPI_DBG_EXIT, + "%s: acpi_bus_unregister_driver\n", ibm->name); + BUG_ON(!ibm->acpi); + acpi_bus_unregister_driver(ibm->acpi->driver); + kfree(ibm->acpi->driver); + ibm->acpi->driver = NULL; + ibm->flags.acpi_driver_registered = 0; + } + + if (ibm->flags.init_called && ibm->exit) { + ibm->exit(); + ibm->flags.init_called = 0; + } + + dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name); +} + static int __init ibm_init(struct ibm_init_struct *iibm) { int ret; @@ -4560,7 +5521,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm) if (ibm->acpi->notify) { ret = setup_acpi_notify(ibm); if (ret == -ENODEV) { - printk(IBM_NOTICE "disabling subdriver %s\n", + printk(TPACPI_NOTICE "disabling subdriver %s\n", ibm->name); ret = 0; goto err_out; @@ -4578,7 +5539,7 @@ static int __init ibm_init(struct ibm_init_struct *iibm) S_IFREG | S_IRUGO | S_IWUSR, proc_dir); if (!entry) { - printk(IBM_ERR "unable to create proc entry %s\n", + printk(TPACPI_ERR "unable to create proc entry %s\n", ibm->name); ret = -ENODEV; goto err_out; @@ -4604,48 +5565,6 @@ err_out: return (ret < 0)? ret : 0; } -static void ibm_exit(struct ibm_struct *ibm) -{ - dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name); - - list_del_init(&ibm->all_drivers); - - if (ibm->flags.acpi_notify_installed) { - dbg_printk(TPACPI_DBG_EXIT, - "%s: acpi_remove_notify_handler\n", ibm->name); - BUG_ON(!ibm->acpi); - acpi_remove_notify_handler(*ibm->acpi->handle, - ibm->acpi->type, - dispatch_acpi_notify); - ibm->flags.acpi_notify_installed = 0; - ibm->flags.acpi_notify_installed = 0; - } - - if (ibm->flags.proc_created) { - dbg_printk(TPACPI_DBG_EXIT, - "%s: remove_proc_entry\n", ibm->name); - remove_proc_entry(ibm->name, proc_dir); - ibm->flags.proc_created = 0; - } - - if (ibm->flags.acpi_driver_registered) { - dbg_printk(TPACPI_DBG_EXIT, - "%s: acpi_bus_unregister_driver\n", ibm->name); - BUG_ON(!ibm->acpi); - acpi_bus_unregister_driver(ibm->acpi->driver); - kfree(ibm->acpi->driver); - ibm->acpi->driver = NULL; - ibm->flags.acpi_driver_registered = 0; - } - - if (ibm->flags.init_called && ibm->exit) { - ibm->exit(); - ibm->flags.init_called = 0; - } - - dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name); -} - /* Probing */ static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp) @@ -4715,10 +5634,10 @@ static int __init probe_for_thinkpad(void) is_thinkpad = (thinkpad_id.model_str != NULL); /* ec is required because many other handles are relative to it */ - IBM_ACPIHANDLE_INIT(ec); + TPACPI_ACPIHANDLE_INIT(ec); if (!ec_handle) { if (is_thinkpad) - printk(IBM_ERR + printk(TPACPI_ERR "Not yet supported ThinkPad detected!\n"); return -ENODEV; } @@ -4839,47 +5758,110 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) return -EINVAL; } -static int experimental; module_param(experimental, int, 0); +MODULE_PARM_DESC(experimental, + "Enables experimental features when non-zero"); -static u32 dbg_level; module_param_named(debug, dbg_level, uint, 0); +MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); -static int force_load; module_param(force_load, bool, 0); +MODULE_PARM_DESC(force_load, + "Attempts to load the driver even on a " + "mis-identified ThinkPad when true"); -static int fan_control_allowed; module_param_named(fan_control, fan_control_allowed, bool, 0); +MODULE_PARM_DESC(fan_control, + "Enables setting fan parameters features when true"); -static int brightness_mode; module_param_named(brightness_mode, brightness_mode, int, 0); +MODULE_PARM_DESC(brightness_mode, + "Selects brightness control strategy: " + "0=auto, 1=EC, 2=CMOS, 3=both"); -static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */ module_param(brightness_enable, uint, 0); +MODULE_PARM_DESC(brightness_enable, + "Enables backlight control when 1, disables when 0"); -static unsigned int hotkey_report_mode; module_param(hotkey_report_mode, uint, 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); +MODULE_PARM_DESC(hotkey_report_mode, + "used for backwards compatibility with userspace, " + "see documentation"); + +#define TPACPI_PARAM(feature) \ + module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ + MODULE_PARM_DESC(feature, "Simulates thinkpad-aci procfs command " \ + "at module load, see documentation") + +TPACPI_PARAM(hotkey); +TPACPI_PARAM(bluetooth); +TPACPI_PARAM(video); +TPACPI_PARAM(light); #ifdef CONFIG_THINKPAD_ACPI_DOCK -IBM_PARAM(dock); +TPACPI_PARAM(dock); #endif #ifdef CONFIG_THINKPAD_ACPI_BAY -IBM_PARAM(bay); +TPACPI_PARAM(bay); #endif /* CONFIG_THINKPAD_ACPI_BAY */ -IBM_PARAM(cmos); -IBM_PARAM(led); -IBM_PARAM(beep); -IBM_PARAM(ecdump); -IBM_PARAM(brightness); -IBM_PARAM(volume); -IBM_PARAM(fan); +TPACPI_PARAM(cmos); +TPACPI_PARAM(led); +TPACPI_PARAM(beep); +TPACPI_PARAM(ecdump); +TPACPI_PARAM(brightness); +TPACPI_PARAM(volume); +TPACPI_PARAM(fan); + +static void thinkpad_acpi_module_exit(void) +{ + struct ibm_struct *ibm, *itmp; + + tpacpi_lifecycle = TPACPI_LIFE_EXITING; + + list_for_each_entry_safe_reverse(ibm, itmp, + &tpacpi_all_drivers, + all_drivers) { + ibm_exit(ibm); + } + + dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); + + if (tpacpi_inputdev) { + if (tp_features.input_device_registered) + input_unregister_device(tpacpi_inputdev); + else + input_free_device(tpacpi_inputdev); + } + + if (tpacpi_hwmon) + hwmon_device_unregister(tpacpi_hwmon); + + if (tp_features.sensors_pdev_attrs_registered) + device_remove_file(&tpacpi_sensors_pdev->dev, + &dev_attr_thinkpad_acpi_pdev_name); + if (tpacpi_sensors_pdev) + platform_device_unregister(tpacpi_sensors_pdev); + if (tpacpi_pdev) + platform_device_unregister(tpacpi_pdev); + + if (tp_features.sensors_pdrv_attrs_registered) + tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver); + if (tp_features.platform_drv_attrs_registered) + tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver); + + if (tp_features.sensors_pdrv_registered) + platform_driver_unregister(&tpacpi_hwmon_pdriver); + + if (tp_features.platform_drv_registered) + platform_driver_unregister(&tpacpi_pdriver); + + if (proc_dir) + remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir); + + kfree(thinkpad_id.bios_version_str); + kfree(thinkpad_id.ec_version_str); + kfree(thinkpad_id.model_str); +} + static int __init thinkpad_acpi_module_init(void) { @@ -4902,12 +5884,13 @@ static int __init thinkpad_acpi_module_init(void) /* Driver initialization */ - IBM_ACPIHANDLE_INIT(ecrd); - IBM_ACPIHANDLE_INIT(ecwr); + TPACPI_ACPIHANDLE_INIT(ecrd); + TPACPI_ACPIHANDLE_INIT(ecwr); - proc_dir = proc_mkdir(IBM_PROC_DIR, acpi_root_dir); + proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir); if (!proc_dir) { - printk(IBM_ERR "unable to create proc dir " IBM_PROC_DIR); + printk(TPACPI_ERR + "unable to create proc dir " TPACPI_PROC_DIR); thinkpad_acpi_module_exit(); return -ENODEV; } @@ -4915,7 +5898,8 @@ static int __init thinkpad_acpi_module_init(void) ret = platform_driver_register(&tpacpi_pdriver); if (ret) { - printk(IBM_ERR "unable to register main platform driver\n"); + printk(TPACPI_ERR + "unable to register main platform driver\n"); thinkpad_acpi_module_exit(); return ret; } @@ -4923,7 +5907,8 @@ static int __init thinkpad_acpi_module_init(void) ret = platform_driver_register(&tpacpi_hwmon_pdriver); if (ret) { - printk(IBM_ERR "unable to register hwmon platform driver\n"); + printk(TPACPI_ERR + "unable to register hwmon platform driver\n"); thinkpad_acpi_module_exit(); return ret; } @@ -4932,10 +5917,12 @@ static int __init thinkpad_acpi_module_init(void) ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver); if (!ret) { tp_features.platform_drv_attrs_registered = 1; - ret = tpacpi_create_driver_attributes(&tpacpi_hwmon_pdriver.driver); + ret = tpacpi_create_driver_attributes( + &tpacpi_hwmon_pdriver.driver); } if (ret) { - printk(IBM_ERR "unable to create sysfs driver attributes\n"); + printk(TPACPI_ERR + "unable to create sysfs driver attributes\n"); thinkpad_acpi_module_exit(); return ret; } @@ -4943,30 +5930,31 @@ static int __init thinkpad_acpi_module_init(void) /* Device initialization */ - tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1, + tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1, NULL, 0); if (IS_ERR(tpacpi_pdev)) { ret = PTR_ERR(tpacpi_pdev); tpacpi_pdev = NULL; - printk(IBM_ERR "unable to register platform device\n"); + printk(TPACPI_ERR "unable to register platform device\n"); thinkpad_acpi_module_exit(); return ret; } tpacpi_sensors_pdev = platform_device_register_simple( - IBM_HWMON_DRVR_NAME, - -1, NULL, 0); + TPACPI_HWMON_DRVR_NAME, + -1, NULL, 0); if (IS_ERR(tpacpi_sensors_pdev)) { ret = PTR_ERR(tpacpi_sensors_pdev); tpacpi_sensors_pdev = NULL; - printk(IBM_ERR "unable to register hwmon platform device\n"); + printk(TPACPI_ERR + "unable to register hwmon platform device\n"); thinkpad_acpi_module_exit(); return ret; } ret = device_create_file(&tpacpi_sensors_pdev->dev, &dev_attr_thinkpad_acpi_pdev_name); if (ret) { - printk(IBM_ERR - "unable to create sysfs hwmon device attributes\n"); + printk(TPACPI_ERR + "unable to create sysfs hwmon device attributes\n"); thinkpad_acpi_module_exit(); return ret; } @@ -4975,20 +5963,20 @@ static int __init thinkpad_acpi_module_init(void) if (IS_ERR(tpacpi_hwmon)) { ret = PTR_ERR(tpacpi_hwmon); tpacpi_hwmon = NULL; - printk(IBM_ERR "unable to register hwmon device\n"); + printk(TPACPI_ERR "unable to register hwmon device\n"); thinkpad_acpi_module_exit(); return ret; } mutex_init(&tpacpi_inputdev_send_mutex); tpacpi_inputdev = input_allocate_device(); if (!tpacpi_inputdev) { - printk(IBM_ERR "unable to allocate input device\n"); + printk(TPACPI_ERR "unable to allocate input device\n"); thinkpad_acpi_module_exit(); return -ENOMEM; } else { /* Prepare input device, but don't register */ tpacpi_inputdev->name = "ThinkPad Extra Buttons"; - tpacpi_inputdev->phys = IBM_DRVR_NAME "/input0"; + tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0"; tpacpi_inputdev->id.bustype = BUS_HOST; tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ? thinkpad_id.vendor : @@ -5007,7 +5995,7 @@ static int __init thinkpad_acpi_module_init(void) } ret = input_register_device(tpacpi_inputdev); if (ret < 0) { - printk(IBM_ERR "unable to register input device\n"); + printk(TPACPI_ERR "unable to register input device\n"); thinkpad_acpi_module_exit(); return ret; } else { @@ -5018,56 +6006,36 @@ static int __init thinkpad_acpi_module_init(void) return 0; } -static void thinkpad_acpi_module_exit(void) -{ - struct ibm_struct *ibm, *itmp; - - tpacpi_lifecycle = TPACPI_LIFE_EXITING; - - list_for_each_entry_safe_reverse(ibm, itmp, - &tpacpi_all_drivers, - all_drivers) { - ibm_exit(ibm); - } - - dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n"); - - if (tpacpi_inputdev) { - if (tp_features.input_device_registered) - input_unregister_device(tpacpi_inputdev); - else - input_free_device(tpacpi_inputdev); - } - - if (tpacpi_hwmon) - hwmon_device_unregister(tpacpi_hwmon); - - if (tp_features.sensors_pdev_attrs_registered) - device_remove_file(&tpacpi_sensors_pdev->dev, - &dev_attr_thinkpad_acpi_pdev_name); - if (tpacpi_sensors_pdev) - platform_device_unregister(tpacpi_sensors_pdev); - if (tpacpi_pdev) - platform_device_unregister(tpacpi_pdev); - - if (tp_features.sensors_pdrv_attrs_registered) - tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver); - if (tp_features.platform_drv_attrs_registered) - tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver); +/* Please remove this in year 2009 */ +MODULE_ALIAS("ibm_acpi"); - if (tp_features.sensors_pdrv_registered) - platform_driver_unregister(&tpacpi_hwmon_pdriver); +/* + * DMI matching for module autoloading + * + * See http://thinkwiki.org/wiki/List_of_DMI_IDs + * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads + * + * Only models listed in thinkwiki will be supported, so add yours + * if it is not there yet. + */ +#define IBM_BIOS_MODULE_ALIAS(__type) \ + MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW") - if (tp_features.platform_drv_registered) - platform_driver_unregister(&tpacpi_pdriver); +/* Non-ancient thinkpads */ +MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*"); +MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*"); - if (proc_dir) - remove_proc_entry(IBM_PROC_DIR, acpi_root_dir); +/* Ancient thinkpad BIOSes have to be identified by + * BIOS type or model number, and there are far less + * BIOS types than model numbers... */ +IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]"); +IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]"); +IBM_BIOS_MODULE_ALIAS("K[U,X-Z]"); - kfree(thinkpad_id.bios_version_str); - kfree(thinkpad_id.ec_version_str); - kfree(thinkpad_id.model_str); -} +MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh"); +MODULE_DESCRIPTION(TPACPI_DESC); +MODULE_VERSION(TPACPI_VERSION); +MODULE_LICENSE("GPL"); module_init(thinkpad_acpi_module_init); module_exit(thinkpad_acpi_module_exit); diff --git a/drivers/misc/thinkpad_acpi.h b/drivers/misc/thinkpad_acpi.h deleted file mode 100644 index 8fba2bb..0000000 --- a/drivers/misc/thinkpad_acpi.h +++ /dev/null @@ -1,606 +0,0 @@ -/* - * thinkpad_acpi.h - 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 __THINKPAD_ACPI_H__ -#define __THINKPAD_ACPI_H__ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -/**************************************************************************** - * Main driver - */ - -#define IBM_NAME "thinkpad" -#define IBM_DESC "ThinkPad ACPI Extras" -#define IBM_FILE IBM_NAME "_acpi" -#define IBM_URL "http://ibm-acpi.sf.net/" -#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net" - -#define IBM_PROC_DIR "ibm" -#define IBM_ACPI_EVENT_PREFIX "ibm" -#define IBM_DRVR_NAME IBM_FILE -#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon" - -#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 - -/* ThinkPad CMOS NVRAM constants */ -#define TP_NVRAM_ADDR_BRIGHTNESS 0x5e -#define TP_NVRAM_MASK_LEVEL_BRIGHTNESS 0x0f -#define TP_NVRAM_POS_LEVEL_BRIGHTNESS 0 - -#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))) - -/* Debugging */ -#define TPACPI_DBG_ALL 0xffff -#define TPACPI_DBG_ALL 0xffff -#define TPACPI_DBG_INIT 0x0001 -#define TPACPI_DBG_EXIT 0x0002 -#define dbg_printk(a_dbg_level, format, arg...) \ - do { if (dbg_level & a_dbg_level) \ - printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0) -#ifdef CONFIG_THINKPAD_ACPI_DEBUG -#define vdbg_printk(a_dbg_level, format, arg...) \ - dbg_printk(a_dbg_level, format, ## arg) -static const char *str_supported(int is_supported); -#else -#define vdbg_printk(a_dbg_level, format, arg...) -#endif - -/* Input IDs */ -#define TPACPI_HKEY_INPUT_VENDOR PCI_VENDOR_ID_IBM -#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */ -#define TPACPI_HKEY_INPUT_VERSION 0x4101 - -/* ACPI HIDs */ -#define IBM_HKEY_HID "IBM0068" - -/* ACPI helpers */ -static int __must_check acpi_evalf(acpi_handle handle, - void *res, char *method, char *fmt, ...); -static int __must_check acpi_ec_read(int i, u8 * p); -static int __must_check acpi_ec_write(int i, u8 v); -static int __must_check _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 drv_acpi_handle_init(char *name, - acpi_handle *handle, acpi_handle parent, - char **paths, int num_paths, char **path); -#define IBM_ACPIHANDLE_INIT(object) \ - drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \ - object##_paths, ARRAY_SIZE(object##_paths), &object##_path) - -/* ThinkPad ACPI helpers */ -static int issue_thinkpad_cmos_command(int cmos_cmd); - -/* procfs support */ -static struct proc_dir_entry *proc_dir; - -/* procfs helpers */ -static int dispatch_procfs_read(char *page, char **start, off_t off, - int count, int *eof, void *data); -static int dispatch_procfs_write(struct file *file, - const char __user * userbuf, - unsigned long count, void *data); -static char *next_cmd(char **cmds); - -/* sysfs support */ -struct attribute_set { - unsigned int members, max_members; - struct attribute_group group; -}; - -static struct attribute_set *create_attr_set(unsigned int max_members, - const char* name); -#define destroy_attr_set(_set) \ - kfree(_set); -static int add_to_attr_set(struct attribute_set* s, struct attribute *attr); -static int add_many_to_attr_set(struct attribute_set* s, - struct attribute **attr, - unsigned int count); -#define register_attr_set_with_sysfs(_attr_set, _kobj) \ - sysfs_create_group(_kobj, &_attr_set->group) -static void delete_attr_set(struct attribute_set* s, struct kobject *kobj); - -static int parse_strtoul(const char *buf, unsigned long max, - unsigned long *value); - -/* Device model */ -static struct platform_device *tpacpi_pdev; -static struct platform_device *tpacpi_sensors_pdev; -static struct device *tpacpi_hwmon; -static struct platform_driver tpacpi_pdriver; -static struct input_dev *tpacpi_inputdev; -static int tpacpi_create_driver_attributes(struct device_driver *drv); -static void tpacpi_remove_driver_attributes(struct device_driver *drv); - -/* Module */ -static int experimental; -static u32 dbg_level; -static int force_load; -static unsigned int hotkey_report_mode; - -static int thinkpad_acpi_module_init(void); -static void thinkpad_acpi_module_exit(void); - - -/**************************************************************************** - * Subdrivers - */ - -struct ibm_struct; - -struct tp_acpi_drv_struct { - const struct acpi_device_id *hid; - struct acpi_driver *driver; - - void (*notify) (struct ibm_struct *, u32); - acpi_handle *handle; - u32 type; - struct acpi_device *device; -}; - -struct ibm_struct { - char *name; - - int (*read) (char *); - int (*write) (char *); - void (*exit) (void); - void (*resume) (void); - - struct list_head all_drivers; - - struct tp_acpi_drv_struct *acpi; - - struct { - u8 acpi_driver_registered:1; - u8 acpi_notify_installed:1; - u8 proc_created:1; - u8 init_called:1; - u8 experimental:1; - } flags; -}; - -struct ibm_init_struct { - char param[32]; - - int (*init) (struct ibm_init_struct *); - struct ibm_struct *data; -}; - -static struct { -#ifdef CONFIG_THINKPAD_ACPI_BAY - u32 bay_status:1; - u32 bay_eject:1; - u32 bay_status2:1; - u32 bay_eject2:1; -#endif - u32 bluetooth:1; - u32 hotkey:1; - u32 hotkey_mask:1; - u32 hotkey_wlsw:1; - u32 light:1; - u32 light_status:1; - u32 bright_16levels:1; - u32 wan:1; - u32 fan_ctrl_status_undef:1; - u32 input_device_registered:1; - u32 platform_drv_registered:1; - u32 platform_drv_attrs_registered:1; - u32 sensors_pdrv_registered:1; - u32 sensors_pdrv_attrs_registered:1; - u32 sensors_pdev_attrs_registered:1; -} tp_features; - -struct thinkpad_id_data { - unsigned int vendor; /* ThinkPad vendor: - * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */ - - char *bios_version_str; /* Something like 1ZET51WW (1.03z) */ - char *ec_version_str; /* Something like 1ZHT51WW-1.04a */ - - u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */ - u16 ec_model; - - char *model_str; -}; - -static struct thinkpad_id_data thinkpad_id; - -static struct list_head tpacpi_all_drivers; - -static struct ibm_init_struct ibms_init[]; -static int set_ibm_param(const char *val, struct kernel_param *kp); -static int ibm_init(struct ibm_init_struct *iibm); -static void ibm_exit(struct ibm_struct *ibm); - - -/* - * procfs master subdriver - */ -static int thinkpad_acpi_driver_init(struct ibm_init_struct *iibm); -static int thinkpad_acpi_driver_read(char *p); - - -/* - * Bay subdriver - */ - -#ifdef CONFIG_THINKPAD_ACPI_BAY -static acpi_handle bay_handle, bay_ej_handle; -static acpi_handle bay2_handle, bay2_ej_handle; - -static int bay_init(struct ibm_init_struct *iibm); -static void bay_notify(struct ibm_struct *ibm, u32 event); -static int bay_read(char *p); -static int bay_write(char *buf); -#endif /* CONFIG_THINKPAD_ACPI_BAY */ - - -/* - * Beep subdriver - */ - -static acpi_handle beep_handle; - -static int beep_read(char *p); -static int beep_write(char *buf); - - -/* - * Bluetooth subdriver - */ - -enum { - /* ACPI GBDC/SBDC bits */ - TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */ - TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */ - TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ -}; - -static int bluetooth_init(struct ibm_init_struct *iibm); -static int bluetooth_get_radiosw(void); -static int bluetooth_set_radiosw(int radio_on); -static int bluetooth_read(char *p); -static int bluetooth_write(char *buf); - - -/* - * Brightness (backlight) subdriver - */ - -#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen" - -static struct backlight_device *ibm_backlight_device; -static int brightness_offset = 0x31; -static int brightness_mode; -static unsigned int brightness_enable; /* 0 = no, 1 = yes, 2 = auto */ - -static int brightness_init(struct ibm_init_struct *iibm); -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_read(char *p); -static int cmos_write(char *buf); - - -/* - * Dock subdriver - */ - -#ifdef CONFIG_THINKPAD_ACPI_DOCK -static acpi_handle pci_handle; -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_THINKPAD_ACPI_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 */ - - TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ - TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ - - TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ -}; - -enum fan_status_access_mode { - TPACPI_FAN_NONE = 0, /* No fan status or control */ - TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */ - TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */ -}; - -enum fan_control_access_mode { - TPACPI_FAN_WR_NONE = 0, /* No fan control */ - TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */ - TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */ - TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */ -}; - -enum fan_control_commands { - TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */ - TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */ - TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd, - * and also watchdog cmd */ -}; - -static int fan_control_allowed; - -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 u8 fan_control_initial_status; -static u8 fan_control_desired_level; -static int fan_watchdog_maxinterval; - -static struct mutex fan_mutex; - -static acpi_handle fans_handle, gfan_handle, sfan_handle; - -static int fan_init(struct ibm_init_struct *iibm); -static void fan_exit(void); -static int fan_get_status(u8 *status); -static int fan_get_status_safe(u8 *status); -static int fan_get_speed(unsigned int *speed); -static void fan_update_desired_level(u8 status); -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_level_safe(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_orig_status; -static u32 hotkey_orig_mask; - -static struct mutex hotkey_mutex; - -static int hotkey_init(struct ibm_init_struct *iibm); -static void hotkey_exit(void); -static int hotkey_get(int *status, u32 *mask); -static int hotkey_set(int status, u32 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 { - TPACPI_LED_NONE = 0, - TPACPI_LED_570, /* 570 */ - TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */ - TPACPI_LED_NEW, /* all others */ -}; - -enum { /* For TPACPI_LED_OLD */ - TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */ - TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */ - TPACPI_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(struct ibm_init_struct *iibm); -static int led_read(char *p); -static int led_write(char *buf); - -/* - * Light (thinklight) subdriver - */ - -static acpi_handle lght_handle, ledb_handle; - -static int light_init(struct ibm_init_struct *iibm); -static int light_read(char *p); -static int light_write(char *buf); - - -/* - * Thermal subdriver - */ - -enum thermal_access_mode { - TPACPI_THERMAL_NONE = 0, /* No thermal support */ - TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */ - TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */ - TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */ - TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */ -}; - -enum { /* TPACPI_THERMAL_TPEC_* */ - TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ - TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ - TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ -}; - -#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ -struct ibm_thermal_sensors_struct { - s32 temp[TPACPI_MAX_THERMAL_SENSORS]; -}; - -static enum thermal_access_mode thermal_read_mode; - -static int thermal_init(struct ibm_init_struct *iibm); -static int thermal_get_sensor(int idx, s32 *value); -static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s); -static int thermal_read(char *p); - - -/* - * Video subdriver - */ - -enum video_access_mode { - TPACPI_VIDEO_NONE = 0, - TPACPI_VIDEO_570, /* 570 */ - TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */ - TPACPI_VIDEO_NEW, /* all others */ -}; - -enum { /* video status flags, based on VIDEO_570 */ - TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */ - TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */ - TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */ -}; - -enum { /* TPACPI_VIDEO_570 constants */ - TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */ - TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to - * video_status_flags */ - TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */ - TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */ -}; - -static enum video_access_mode video_supported; -static int video_orig_autosw; -static acpi_handle vid_handle, vid2_handle; - -static int video_init(struct ibm_init_struct *iibm); -static void video_exit(void); -static int video_outputsw_get(void); -static int video_outputsw_set(int status); -static int video_autosw_get(void); -static int video_autosw_set(int enable); -static int video_outputsw_cycle(void); -static int video_expand_toggle(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 - */ - -enum { - /* ACPI GWAN/SWAN bits */ - TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */ - TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */ - TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ -}; - -static int wan_init(struct ibm_init_struct *iibm); -static int wan_get_radiosw(void); -static int wan_set_radiosw(int radio_on); -static int wan_read(char *p); -static int wan_write(char *buf); - - -#endif /* __THINKPAD_ACPI_H */ diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c index dada899..4e6ea61 100644 --- a/drivers/pnp/pnpacpi/core.c +++ b/drivers/pnp/pnpacpi/core.c @@ -166,9 +166,13 @@ static int __init pnpacpi_add_device(struct acpi_device *device) struct pnp_id *dev_id; struct pnp_dev *dev; + /* + * If a PnPacpi device is not present , the device + * driver should not be loaded. + */ status = acpi_get_handle(device->handle, "_CRS", &temp); if (ACPI_FAILURE(status) || !ispnpidacpi(acpi_device_hid(device)) || - is_exclusive_device(device)) + is_exclusive_device(device) || !device->status.present) return 0; dev = kzalloc(sizeof(struct pnp_dev), GFP_KERNEL); diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index f85f77a..581daa4 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -48,6 +48,7 @@ #define ACPI_BUTTON_HID_SLEEPF "LNXSLPBN" #define ACPI_VIDEO_HID "LNXVIDEO" #define ACPI_BAY_HID "LNXIOBAY" +#define ACPI_DOCK_HID "LNXDOCK" /* -------------------------------------------------------------------------- PCI diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 37bfa19..ff8fc30 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -142,6 +142,7 @@ header-y += snmp.h header-y += sockios.h header-y += som.h header-y += sound.h +header-y += suspend_ioctls.h header-y += taskstats.h header-y += telephony.h header-y += termios.h diff --git a/include/linux/acpi.h b/include/linux/acpi.h index e3c16c9..67984e6 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -79,7 +79,6 @@ typedef int (*acpi_table_handler) (struct acpi_table_header *table); typedef int (*acpi_table_entry_handler) (struct acpi_subtable_header *header, const unsigned long end); char * __acpi_map_table (unsigned long phys_addr, unsigned long size); -unsigned long acpi_find_rsdp (void); int acpi_boot_init (void); int acpi_boot_table_init (void); int acpi_numa_init (void); diff --git a/include/linux/notifier.h b/include/linux/notifier.h index 0c40cc0..6cc7b60 100644 --- a/include/linux/notifier.h +++ b/include/linux/notifier.h @@ -230,6 +230,8 @@ static inline int notifier_to_errno(int ret) #define PM_POST_HIBERNATION 0x0002 /* Hibernation finished */ #define PM_SUSPEND_PREPARE 0x0003 /* Going to suspend the system */ #define PM_POST_SUSPEND 0x0004 /* Suspend finished */ +#define PM_RESTORE_PREPARE 0x0005 /* Going to restore a saved image */ +#define PM_POST_RESTORE 0x0006 /* Restore failed */ /* Console keyboard events. * Note: KBD_KEYCODE is always sent before KBD_UNBOUND_KEYCODE, KBD_UNICODE and diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 4360e08..837da6c 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -216,17 +216,8 @@ void __save_processor_state(struct saved_context *ctxt); void __restore_processor_state(struct saved_context *ctxt); /* kernel/power/main.c */ -extern struct blocking_notifier_head pm_chain_head; - -static inline int register_pm_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&pm_chain_head, nb); -} - -static inline int unregister_pm_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&pm_chain_head, nb); -} +extern int register_pm_notifier(struct notifier_block *nb); +extern int unregister_pm_notifier(struct notifier_block *nb); #define pm_notifier(fn, pri) { \ static struct notifier_block fn##_nb = \ diff --git a/include/linux/suspend_ioctls.h b/include/linux/suspend_ioctls.h new file mode 100644 index 0000000..2c6faec --- /dev/null +++ b/include/linux/suspend_ioctls.h @@ -0,0 +1,32 @@ +#ifndef _LINUX_SUSPEND_IOCTLS_H +#define _LINUX_SUSPEND_IOCTLS_H + +/* + * This structure is used to pass the values needed for the identification + * of the resume swap area from a user space to the kernel via the + * SNAPSHOT_SET_SWAP_AREA ioctl + */ +struct resume_swap_area { + loff_t offset; + u_int32_t dev; +} __attribute__((packed)); + +#define SNAPSHOT_IOC_MAGIC '3' +#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1) +#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2) +#define SNAPSHOT_ATOMIC_RESTORE _IO(SNAPSHOT_IOC_MAGIC, 4) +#define SNAPSHOT_FREE _IO(SNAPSHOT_IOC_MAGIC, 5) +#define SNAPSHOT_FREE_SWAP_PAGES _IO(SNAPSHOT_IOC_MAGIC, 9) +#define SNAPSHOT_S2RAM _IO(SNAPSHOT_IOC_MAGIC, 11) +#define SNAPSHOT_SET_SWAP_AREA _IOW(SNAPSHOT_IOC_MAGIC, 13, \ + struct resume_swap_area) +#define SNAPSHOT_GET_IMAGE_SIZE _IOR(SNAPSHOT_IOC_MAGIC, 14, loff_t) +#define SNAPSHOT_PLATFORM_SUPPORT _IO(SNAPSHOT_IOC_MAGIC, 15) +#define SNAPSHOT_POWER_OFF _IO(SNAPSHOT_IOC_MAGIC, 16) +#define SNAPSHOT_CREATE_IMAGE _IOW(SNAPSHOT_IOC_MAGIC, 17, int) +#define SNAPSHOT_PREF_IMAGE_SIZE _IO(SNAPSHOT_IOC_MAGIC, 18) +#define SNAPSHOT_AVAIL_SWAP_SIZE _IOR(SNAPSHOT_IOC_MAGIC, 19, loff_t) +#define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, loff_t) +#define SNAPSHOT_IOC_MAXNR 20 + +#endif /* _LINUX_SUSPEND_IOCTLS_H */ diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 8e186c6..f8153fd 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -44,9 +44,30 @@ config PM_VERBOSE ---help--- This option enables verbose messages from the Power Management code. +config CAN_PM_TRACE + def_bool y + depends on PM_DEBUG && PM_SLEEP && EXPERIMENTAL + config PM_TRACE + bool + help + This enables code to save the last PM event point across + reboot. The architecture needs to support this, x86 for + example does by saving things in the RTC, see below. + + The architecture specific code must provide the extern + functions from as well as the + header with a TRACE_RESUME() macro. + + The way the information is presented is architecture- + dependent, x86 will print the information during a + late_initcall. + +config PM_TRACE_RTC bool "Suspend/resume event tracing" - depends on PM_DEBUG && X86 && PM_SLEEP && EXPERIMENTAL + depends on CAN_PM_TRACE + depends on X86 + select PM_TRACE default n ---help--- This enables some cheesy code to save the last PM event point in the @@ -63,7 +84,8 @@ config PM_TRACE config PM_SLEEP_SMP bool - depends on SUSPEND_SMP_POSSIBLE || HIBERNATION_SMP_POSSIBLE + depends on SMP + depends on ARCH_SUSPEND_POSSIBLE || ARCH_HIBERNATION_POSSIBLE depends on PM_SLEEP select HOTPLUG_CPU default y @@ -73,46 +95,18 @@ config PM_SLEEP depends on SUSPEND || HIBERNATION default y -config SUSPEND_UP_POSSIBLE - bool - depends on (X86 && !X86_VOYAGER) || PPC || ARM || BLACKFIN || MIPS \ - || SUPERH || FRV - depends on !SMP - default y - -config SUSPEND_SMP_POSSIBLE - bool - depends on (X86 && !X86_VOYAGER) \ - || (PPC && (PPC_PSERIES || PPC_PMAC)) || ARM - depends on SMP - default y - config SUSPEND bool "Suspend to RAM and standby" - depends on PM - depends on SUSPEND_UP_POSSIBLE || SUSPEND_SMP_POSSIBLE + depends on PM && ARCH_SUSPEND_POSSIBLE default y ---help--- Allow the system to enter sleep states in which main memory is powered and thus its contents are preserved, such as the - suspend-to-RAM state (i.e. the ACPI S3 state). - -config HIBERNATION_UP_POSSIBLE - bool - depends on X86 || PPC64_SWSUSP || PPC32 - depends on !SMP - default y - -config HIBERNATION_SMP_POSSIBLE - bool - depends on (X86 && !X86_VOYAGER) || PPC64_SWSUSP - depends on SMP - default y + suspend-to-RAM state (e.g. the ACPI S3 state). config HIBERNATION bool "Hibernation (aka 'suspend to disk')" - depends on PM && SWAP - depends on HIBERNATION_UP_POSSIBLE || HIBERNATION_SMP_POSSIBLE + depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE ---help--- Enable the suspend to disk (STD) functionality, which is usually called "hibernation" in user interfaces. STD checkpoints the diff --git a/kernel/power/disk.c b/kernel/power/disk.c index 05b6479..2fce651 100644 --- a/kernel/power/disk.c +++ b/kernel/power/disk.c @@ -70,6 +70,35 @@ void hibernation_set_ops(struct platform_hibernation_ops *ops) mutex_unlock(&pm_mutex); } +#ifdef CONFIG_PM_DEBUG +static void hibernation_debug_sleep(void) +{ + printk(KERN_INFO "hibernation debug: Waiting for 5 seconds.\n"); + mdelay(5000); +} + +static int hibernation_testmode(int mode) +{ + if (hibernation_mode == mode) { + hibernation_debug_sleep(); + return 1; + } + return 0; +} + +static int hibernation_test(int level) +{ + if (pm_test_level == level) { + hibernation_debug_sleep(); + return 1; + } + return 0; +} +#else /* !CONFIG_PM_DEBUG */ +static int hibernation_testmode(int mode) { return 0; } +static int hibernation_test(int level) { return 0; } +#endif /* !CONFIG_PM_DEBUG */ + /** * platform_start - tell the platform driver that we're starting * hibernation @@ -162,19 +191,25 @@ int create_image(int platform_mode) */ error = device_power_down(PMSG_FREEZE); if (error) { - printk(KERN_ERR "Some devices failed to power down, " - KERN_ERR "aborting suspend\n"); + printk(KERN_ERR "PM: Some devices failed to power down, " + "aborting hibernation\n"); goto Enable_irqs; } + if (hibernation_test(TEST_CORE)) + goto Power_up; + + in_suspend = 1; save_processor_state(); error = swsusp_arch_suspend(); if (error) - printk(KERN_ERR "Error %d while creating the image\n", error); + printk(KERN_ERR "PM: Error %d creating hibernation image\n", + error); /* Restore control flow magically appears here */ restore_processor_state(); if (!in_suspend) platform_leave(platform_mode); + Power_up: /* NOTE: device_power_up() is just a resume() for devices * that suspended with irqs off ... no overall powerup. */ @@ -211,24 +246,29 @@ int hibernation_snapshot(int platform_mode) if (error) goto Resume_console; - error = platform_pre_snapshot(platform_mode); - if (error) + if (hibernation_test(TEST_DEVICES)) goto Resume_devices; + error = platform_pre_snapshot(platform_mode); + if (error || hibernation_test(TEST_PLATFORM)) + goto Finish; + error = disable_nonboot_cpus(); if (!error) { - if (hibernation_mode != HIBERNATION_TEST) { - in_suspend = 1; - error = create_image(platform_mode); - /* Control returns here after successful restore */ - } else { - printk("swsusp debug: Waiting for 5 seconds.\n"); - mdelay(5000); - } + if (hibernation_test(TEST_CPUS)) + goto Enable_cpus; + + if (hibernation_testmode(HIBERNATION_TEST)) + goto Enable_cpus; + + error = create_image(platform_mode); + /* Control returns here after successful restore */ } + Enable_cpus: enable_nonboot_cpus(); - Resume_devices: + Finish: platform_finish(platform_mode); + Resume_devices: device_resume(); Resume_console: resume_console(); @@ -236,6 +276,53 @@ int hibernation_snapshot(int platform_mode) } /** + * resume_target_kernel - prepare devices that need to be suspended with + * interrupts off, restore the contents of highmem that have not been + * restored yet from the image and run the low level code that will restore + * the remaining contents of memory and switch to the just restored target + * kernel. + */ + +static int resume_target_kernel(void) +{ + int error; + + local_irq_disable(); + error = device_power_down(PMSG_PRETHAW); + if (error) { + printk(KERN_ERR "PM: Some devices failed to power down, " + "aborting resume\n"); + goto Enable_irqs; + } + /* We'll ignore saved state, but this gets preempt count (etc) right */ + save_processor_state(); + error = restore_highmem(); + if (!error) { + error = swsusp_arch_resume(); + /* + * The code below is only ever reached in case of a failure. + * Otherwise execution continues at place where + * swsusp_arch_suspend() was called + */ + BUG_ON(!error); + /* This call to restore_highmem() undos the previous one */ + restore_highmem(); + } + /* + * The only reason why swsusp_arch_resume() can fail is memory being + * very tight, so we have to free it as soon as we can to avoid + * subsequent failures + */ + swsusp_free(); + restore_processor_state(); + touch_softlockup_watchdog(); + device_power_up(); + Enable_irqs: + local_irq_enable(); + return error; +} + +/** * hibernation_restore - quiesce devices and restore the hibernation * snapshot image. If successful, control returns in hibernation_snaphot() * @platform_mode - if set, use the platform driver, if available, to @@ -258,7 +345,7 @@ int hibernation_restore(int platform_mode) if (!error) { error = disable_nonboot_cpus(); if (!error) - error = swsusp_resume(); + error = resume_target_kernel(); enable_nonboot_cpus(); } platform_restore_cleanup(platform_mode); @@ -352,7 +439,7 @@ static void power_down(void) * Valid image is on the disk, if we continue we risk serious data * corruption after resume. */ - printk(KERN_CRIT "Please power me down manually\n"); + printk(KERN_CRIT "PM: Please power down manually\n"); while(1); } @@ -398,7 +485,7 @@ int hibernate(void) if (error) goto Exit; - printk("Syncing filesystems ... "); + printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done.\n"); @@ -406,11 +493,12 @@ int hibernate(void) if (error) goto Finish; - if (hibernation_mode == HIBERNATION_TESTPROC) { - printk("swsusp debug: Waiting for 5 seconds.\n"); - mdelay(5000); + if (hibernation_test(TEST_FREEZER)) goto Thaw; - } + + if (hibernation_testmode(HIBERNATION_TESTPROC)) + goto Thaw; + error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM); if (in_suspend && !error) { unsigned int flags = 0; @@ -473,22 +561,23 @@ static int software_resume(void) return -ENOENT; } swsusp_resume_device = name_to_dev_t(resume_file); - pr_debug("swsusp: Resume From Partition %s\n", resume_file); + pr_debug("PM: Resume from partition %s\n", resume_file); } else { - pr_debug("swsusp: Resume From Partition %d:%d\n", - MAJOR(swsusp_resume_device), MINOR(swsusp_resume_device)); + pr_debug("PM: Resume from partition %d:%d\n", + MAJOR(swsusp_resume_device), + MINOR(swsusp_resume_device)); } if (noresume) { /** - * FIXME: If noresume is specified, we need to find the partition - * and reset it back to normal swap space. + * FIXME: If noresume is specified, we need to find the + * partition and reset it back to normal swap space. */ mutex_unlock(&pm_mutex); return 0; } - pr_debug("PM: Checking swsusp image.\n"); + pr_debug("PM: Checking hibernation image.\n"); error = swsusp_check(); if (error) goto Unlock; @@ -499,6 +588,10 @@ static int software_resume(void) goto Unlock; } + error = pm_notifier_call_chain(PM_RESTORE_PREPARE); + if (error) + goto Finish; + error = create_basic_memory_bitmaps(); if (error) goto Finish; @@ -510,7 +603,7 @@ static int software_resume(void) goto Done; } - pr_debug("PM: Reading swsusp image.\n"); + pr_debug("PM: Reading hibernation image.\n"); error = swsusp_read(&flags); if (!error) @@ -522,6 +615,7 @@ static int software_resume(void) Done: free_basic_memory_bitmaps(); Finish: + pm_notifier_call_chain(PM_POST_RESTORE); atomic_inc(&snapshot_device_available); /* For success case, the suspend path will release the lock */ Unlock: @@ -634,7 +728,7 @@ static ssize_t disk_store(struct kset *kset, const char *buf, size_t n) error = -EINVAL; if (!error) - pr_debug("PM: suspend-to-disk mode set to '%s'\n", + pr_debug("PM: Hibernation mode set to '%s'\n", hibernation_modes[mode]); mutex_unlock(&pm_mutex); return error ? error : n; @@ -664,7 +758,7 @@ static ssize_t resume_store(struct kset *kset, const char *buf, size_t n) mutex_lock(&pm_mutex); swsusp_resume_device = res; mutex_unlock(&pm_mutex); - printk("Attempting manual resume\n"); + printk(KERN_INFO "PM: Starting manual resume from disk\n"); noresume = 0; software_resume(); ret = n; diff --git a/kernel/power/main.c b/kernel/power/main.c index 3cdf95b..1208874 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -24,12 +24,109 @@ #include "power.h" -BLOCKING_NOTIFIER_HEAD(pm_chain_head); - DEFINE_MUTEX(pm_mutex); +#ifdef CONFIG_PM_SLEEP + +/* Routines for PM-transition notifications */ + +static BLOCKING_NOTIFIER_HEAD(pm_chain_head); + +int register_pm_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&pm_chain_head, nb); +} +EXPORT_SYMBOL_GPL(register_pm_notifier); + +int unregister_pm_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&pm_chain_head, nb); +} +EXPORT_SYMBOL_GPL(unregister_pm_notifier); + +int pm_notifier_call_chain(unsigned long val) +{ + return (blocking_notifier_call_chain(&pm_chain_head, val, NULL) + == NOTIFY_BAD) ? -EINVAL : 0; +} + +#endif /* CONFIG_PM_SLEEP */ + #ifdef CONFIG_SUSPEND +#ifdef CONFIG_PM_DEBUG +int pm_test_level = TEST_NONE; + +static int suspend_test(int level) +{ + if (pm_test_level == level) { + printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n"); + mdelay(5000); + return 1; + } + return 0; +} + +static const char * const pm_tests[__TEST_AFTER_LAST] = { + [TEST_NONE] = "none", + [TEST_CORE] = "core", + [TEST_CPUS] = "processors", + [TEST_PLATFORM] = "platform", + [TEST_DEVICES] = "devices", + [TEST_FREEZER] = "freezer", +}; + +static ssize_t pm_test_show(struct kset *kset, char *buf) +{ + char *s = buf; + int level; + + for (level = TEST_FIRST; level <= TEST_MAX; level++) + if (pm_tests[level]) { + if (level == pm_test_level) + s += sprintf(s, "[%s] ", pm_tests[level]); + else + s += sprintf(s, "%s ", pm_tests[level]); + } + + if (s != buf) + /* convert the last space to a newline */ + *(s-1) = '\n'; + + return (s - buf); +} + +static ssize_t pm_test_store(struct kset *kset, const char *buf, size_t n) +{ + const char * const *s; + int level; + char *p; + int len; + int error = -EINVAL; + + p = memchr(buf, '\n', n); + len = p ? p - buf : n; + + mutex_lock(&pm_mutex); + + level = TEST_FIRST; + for (s = &pm_tests[level]; level <= TEST_MAX; s++, level++) + if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) { + pm_test_level = level; + error = 0; + break; + } + + mutex_unlock(&pm_mutex); + + return error ? error : n; +} + +power_attr(pm_test); +#else /* !CONFIG_PM_DEBUG */ +static inline int suspend_test(int level) { return 0; } +#endif /* !CONFIG_PM_DEBUG */ + /* This is just an arbitrary number */ #define FREE_PAGE_NUMBER (100) @@ -130,10 +227,13 @@ static int suspend_enter(suspend_state_t state) BUG_ON(!irqs_disabled()); if ((error = device_power_down(PMSG_SUSPEND))) { - printk(KERN_ERR "Some devices failed to power down\n"); + printk(KERN_ERR "PM: Some devices failed to power down\n"); goto Done; } - error = suspend_ops->enter(state); + + if (!suspend_test(TEST_CORE)) + error = suspend_ops->enter(state); + device_power_up(); Done: arch_suspend_enable_irqs(); @@ -142,8 +242,8 @@ static int suspend_enter(suspend_state_t state) } /** - * suspend_devices_and_enter - suspend devices and enter the desired system sleep - * state. + * suspend_devices_and_enter - suspend devices and enter the desired system + * sleep state. * @state: state to enter */ int suspend_devices_and_enter(suspend_state_t state) @@ -161,19 +261,28 @@ int suspend_devices_and_enter(suspend_state_t state) suspend_console(); error = device_suspend(PMSG_SUSPEND); if (error) { - printk(KERN_ERR "Some devices failed to suspend\n"); + printk(KERN_ERR "PM: Some devices failed to suspend\n"); goto Resume_console; } + + if (suspend_test(TEST_DEVICES)) + goto Resume_devices; + if (suspend_ops->prepare) { error = suspend_ops->prepare(); if (error) goto Resume_devices; } + + if (suspend_test(TEST_PLATFORM)) + goto Finish; + error = disable_nonboot_cpus(); - if (!error) + if (!error && !suspend_test(TEST_CPUS)) suspend_enter(state); enable_nonboot_cpus(); + Finish: if (suspend_ops->finish) suspend_ops->finish(); Resume_devices: @@ -235,17 +344,22 @@ static int enter_state(suspend_state_t state) if (!mutex_trylock(&pm_mutex)) return -EBUSY; - printk("Syncing filesystems ... "); + printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done.\n"); pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); - if ((error = suspend_prepare())) + error = suspend_prepare(); + if (error) goto Unlock; + if (suspend_test(TEST_FREEZER)) + goto Finish; + pr_debug("PM: Entering %s sleep\n", pm_states[state]); error = suspend_devices_and_enter(state); + Finish: pr_debug("PM: Finishing wakeup.\n"); suspend_finish(); Unlock: @@ -363,18 +477,18 @@ pm_trace_store(struct kset *kset, const char *buf, size_t n) } power_attr(pm_trace); +#endif /* CONFIG_PM_TRACE */ static struct attribute * g[] = { &state_attr.attr, +#ifdef CONFIG_PM_TRACE &pm_trace_attr.attr, +#endif +#ifdef CONFIG_PM_DEBUG + &pm_test_attr.attr, +#endif NULL, }; -#else -static struct attribute * g[] = { - &state_attr.attr, - NULL, -}; -#endif /* CONFIG_PM_TRACE */ static struct attribute_group attr_group = { .attrs = g, diff --git a/kernel/power/power.h b/kernel/power/power.h index 195dc46..cd12589 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -1,4 +1,5 @@ #include +#include #include struct swsusp_info { @@ -130,42 +131,12 @@ struct snapshot_handle { #define data_of(handle) ((handle).buffer + (handle).buf_offset) extern unsigned int snapshot_additional_pages(struct zone *zone); +extern unsigned long snapshot_get_image_size(void); extern int snapshot_read_next(struct snapshot_handle *handle, size_t count); extern int snapshot_write_next(struct snapshot_handle *handle, size_t count); extern void snapshot_write_finalize(struct snapshot_handle *handle); extern int snapshot_image_loaded(struct snapshot_handle *handle); -/* - * This structure is used to pass the values needed for the identification - * of the resume swap area from a user space to the kernel via the - * SNAPSHOT_SET_SWAP_AREA ioctl - */ -struct resume_swap_area { - loff_t offset; - u_int32_t dev; -} __attribute__((packed)); - -#define SNAPSHOT_IOC_MAGIC '3' -#define SNAPSHOT_FREEZE _IO(SNAPSHOT_IOC_MAGIC, 1) -#define SNAPSHOT_UNFREEZE _IO(SNAPSHOT_IOC_MAGIC, 2) -#define SNAPSHOT_ATOMIC_SNAPSHOT _IOW(SNAPSHOT_IOC_MAGIC, 3, void *) -#define SNAPSHOT_ATOMIC_RESTORE _IO(SNAPSHOT_IOC_MAGIC, 4) -#define SNAPSHOT_FREE _IO(SNAPSHOT_IOC_MAGIC, 5) -#define SNAPSHOT_SET_IMAGE_SIZE _IOW(SNAPSHOT_IOC_MAGIC, 6, unsigned long) -#define SNAPSHOT_AVAIL_SWAP _IOR(SNAPSHOT_IOC_MAGIC, 7, void *) -#define SNAPSHOT_GET_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 8, void *) -#define SNAPSHOT_FREE_SWAP_PAGES _IO(SNAPSHOT_IOC_MAGIC, 9) -#define SNAPSHOT_SET_SWAP_FILE _IOW(SNAPSHOT_IOC_MAGIC, 10, unsigned int) -#define SNAPSHOT_S2RAM _IO(SNAPSHOT_IOC_MAGIC, 11) -#define SNAPSHOT_PMOPS _IOW(SNAPSHOT_IOC_MAGIC, 12, unsigned int) -#define SNAPSHOT_SET_SWAP_AREA _IOW(SNAPSHOT_IOC_MAGIC, 13, \ - struct resume_swap_area) -#define SNAPSHOT_IOC_MAXNR 13 - -#define PMOPS_PREPARE 1 -#define PMOPS_ENTER 2 -#define PMOPS_FINISH 3 - /* If unset, the snapshot device cannot be open. */ extern atomic_t snapshot_device_available; @@ -183,7 +154,6 @@ extern int swsusp_swap_in_use(void); extern int swsusp_check(void); extern int swsusp_shrink_memory(void); extern void swsusp_free(void); -extern int swsusp_resume(void); extern int swsusp_read(unsigned int *flags_p); extern int swsusp_write(unsigned int flags); extern void swsusp_close(void); @@ -203,11 +173,35 @@ static inline int suspend_devices_and_enter(suspend_state_t state) } #endif /* !CONFIG_SUSPEND */ -/* kernel/power/common.c */ -extern struct blocking_notifier_head pm_chain_head; +#ifdef CONFIG_PM_SLEEP +/* kernel/power/main.c */ +extern int pm_notifier_call_chain(unsigned long val); +#endif -static inline int pm_notifier_call_chain(unsigned long val) -{ - return (blocking_notifier_call_chain(&pm_chain_head, val, NULL) - == NOTIFY_BAD) ? -EINVAL : 0; -} +#ifdef CONFIG_HIGHMEM +unsigned int count_highmem_pages(void); +int restore_highmem(void); +#else +static inline unsigned int count_highmem_pages(void) { return 0; } +static inline int restore_highmem(void) { return 0; } +#endif + +/* + * Suspend test levels + */ +enum { + /* keep first */ + TEST_NONE, + TEST_CORE, + TEST_CPUS, + TEST_PLATFORM, + TEST_DEVICES, + TEST_FREEZER, + /* keep last */ + __TEST_AFTER_LAST +}; + +#define TEST_FIRST TEST_NONE +#define TEST_MAX (__TEST_AFTER_LAST - 1) + +extern int pm_test_level; diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 78039b4..f6a5df9 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -635,7 +635,7 @@ __register_nosave_region(unsigned long start_pfn, unsigned long end_pfn, region->end_pfn = end_pfn; list_add_tail(®ion->list, &nosave_regions); Report: - printk("swsusp: Registered nosave memory region: %016lx - %016lx\n", + printk(KERN_INFO "PM: Registered nosave memory: %016lx - %016lx\n", start_pfn << PAGE_SHIFT, end_pfn << PAGE_SHIFT); } @@ -704,7 +704,7 @@ static void mark_nosave_pages(struct memory_bitmap *bm) list_for_each_entry(region, &nosave_regions, list) { unsigned long pfn; - printk("swsusp: Marking nosave pages: %016lx - %016lx\n", + pr_debug("PM: Marking nosave pages: %016lx - %016lx\n", region->start_pfn << PAGE_SHIFT, region->end_pfn << PAGE_SHIFT); @@ -749,7 +749,7 @@ int create_basic_memory_bitmaps(void) free_pages_map = bm2; mark_nosave_pages(forbidden_pages_map); - printk("swsusp: Basic memory bitmaps created\n"); + pr_debug("PM: Basic memory bitmaps created\n"); return 0; @@ -784,7 +784,7 @@ void free_basic_memory_bitmaps(void) memory_bm_free(bm2, PG_UNSAFE_CLEAR); kfree(bm2); - printk("swsusp: Basic memory bitmaps freed\n"); + pr_debug("PM: Basic memory bitmaps freed\n"); } /** @@ -872,7 +872,6 @@ unsigned int count_highmem_pages(void) } #else static inline void *saveable_highmem_page(unsigned long pfn) { return NULL; } -static inline unsigned int count_highmem_pages(void) { return 0; } #endif /* CONFIG_HIGHMEM */ /** @@ -1089,7 +1088,7 @@ static int enough_free_mem(unsigned int nr_pages, unsigned int nr_highmem) } nr_pages += count_pages_for_highmem(nr_highmem); - pr_debug("swsusp: Normal pages needed: %u + %u + %u, available pages: %u\n", + pr_debug("PM: Normal pages needed: %u + %u + %u, available pages: %u\n", nr_pages, PAGES_FOR_IO, meta, free); return free > nr_pages + PAGES_FOR_IO + meta; @@ -1202,20 +1201,20 @@ asmlinkage int swsusp_save(void) { unsigned int nr_pages, nr_highmem; - printk("swsusp: critical section: \n"); + printk(KERN_INFO "PM: Creating hibernation image: \n"); drain_local_pages(); nr_pages = count_data_pages(); nr_highmem = count_highmem_pages(); - printk("swsusp: Need to copy %u pages\n", nr_pages + nr_highmem); + printk(KERN_INFO "PM: Need to copy %u pages\n", nr_pages + nr_highmem); if (!enough_free_mem(nr_pages, nr_highmem)) { - printk(KERN_ERR "swsusp: Not enough free memory\n"); + printk(KERN_ERR "PM: Not enough free memory\n"); return -ENOMEM; } if (swsusp_alloc(&orig_bm, ©_bm, nr_pages, nr_highmem)) { - printk(KERN_ERR "swsusp: Memory allocation failed\n"); + printk(KERN_ERR "PM: Memory allocation failed\n"); return -ENOMEM; } @@ -1235,7 +1234,8 @@ asmlinkage int swsusp_save(void) nr_copy_pages = nr_pages; nr_meta_pages = DIV_ROUND_UP(nr_pages * sizeof(long), PAGE_SIZE); - printk("swsusp: critical section: done (%d pages copied)\n", nr_pages); + printk(KERN_INFO "PM: Hibernation image created (%d pages copied)\n", + nr_pages); return 0; } @@ -1264,12 +1264,17 @@ static char *check_image_kernel(struct swsusp_info *info) } #endif /* CONFIG_ARCH_HIBERNATION_HEADER */ +unsigned long snapshot_get_image_size(void) +{ + return nr_copy_pages + nr_meta_pages + 1; +} + static int init_header(struct swsusp_info *info) { memset(info, 0, sizeof(struct swsusp_info)); info->num_physpages = num_physpages; info->image_pages = nr_copy_pages; - info->pages = nr_copy_pages + nr_meta_pages + 1; + info->pages = snapshot_get_image_size(); info->size = info->pages; info->size <<= PAGE_SHIFT; return init_header_complete(info); @@ -1429,7 +1434,7 @@ static int check_header(struct swsusp_info *info) if (!reason && info->num_physpages != num_physpages) reason = "memory size"; if (reason) { - printk(KERN_ERR "swsusp: Resume mismatch: %s\n", reason); + printk(KERN_ERR "PM: Image mismatch: %s\n", reason); return -EPERM; } return 0; diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 917aba1..a0abf9a 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -28,8 +28,6 @@ #include "power.h" -extern char resume_file[]; - #define SWSUSP_SIG "S1SUSPEND" struct swsusp_header { @@ -73,7 +71,8 @@ static int submit(int rw, pgoff_t page_off, struct page *page, bio->bi_end_io = end_swap_bio_read; if (bio_add_page(bio, page, PAGE_SIZE, 0) < PAGE_SIZE) { - printk("swsusp: ERROR: adding page to bio at %ld\n", page_off); + printk(KERN_ERR "PM: Adding page to bio failed at %ld\n", + page_off); bio_put(bio); return -EFAULT; } @@ -153,7 +152,7 @@ static int mark_swapfiles(sector_t start, unsigned int flags) error = bio_write_page(swsusp_resume_block, swsusp_header, NULL); } else { - printk(KERN_ERR "swsusp: Swap header not found!\n"); + printk(KERN_ERR "PM: Swap header not found!\n"); error = -ENODEV; } return error; @@ -325,7 +324,8 @@ static int save_image(struct swap_map_handle *handle, struct timeval start; struct timeval stop; - printk("Saving image data pages (%u pages) ... ", nr_to_write); + printk(KERN_INFO "PM: Saving image data pages (%u pages) ... ", + nr_to_write); m = nr_to_write / 100; if (!m) m = 1; @@ -365,7 +365,7 @@ static int enough_swap(unsigned int nr_pages) { unsigned int free_swap = count_swap_pages(root_swap, 1); - pr_debug("swsusp: free swap pages: %u\n", free_swap); + pr_debug("PM: Free swap pages: %u\n", free_swap); return free_swap > nr_pages + PAGES_FOR_IO; } @@ -388,7 +388,7 @@ int swsusp_write(unsigned int flags) error = swsusp_swap_check(); if (error) { - printk(KERN_ERR "swsusp: Cannot find swap device, try " + printk(KERN_ERR "PM: Cannot find swap device, try " "swapon -a.\n"); return error; } @@ -402,7 +402,7 @@ int swsusp_write(unsigned int flags) } header = (struct swsusp_info *)data_of(snapshot); if (!enough_swap(header->pages)) { - printk(KERN_ERR "swsusp: Not enough free swap\n"); + printk(KERN_ERR "PM: Not enough free swap\n"); error = -ENOSPC; goto out; } @@ -417,7 +417,7 @@ int swsusp_write(unsigned int flags) if (!error) { flush_swap_writer(&handle); - printk("S"); + printk(KERN_INFO "PM: S"); error = mark_swapfiles(start, flags); printk("|\n"); } @@ -507,7 +507,8 @@ static int load_image(struct swap_map_handle *handle, int err2; unsigned nr_pages; - printk("Loading image data pages (%u pages) ... ", nr_to_read); + printk(KERN_INFO "PM: Loading image data pages (%u pages) ... ", + nr_to_read); m = nr_to_read / 100; if (!m) m = 1; @@ -558,7 +559,7 @@ int swsusp_read(unsigned int *flags_p) *flags_p = swsusp_header->flags; if (IS_ERR(resume_bdev)) { - pr_debug("swsusp: block device not initialised\n"); + pr_debug("PM: Image device not initialised\n"); return PTR_ERR(resume_bdev); } @@ -577,9 +578,9 @@ int swsusp_read(unsigned int *flags_p) blkdev_put(resume_bdev); if (!error) - pr_debug("swsusp: Reading resume file was successful\n"); + pr_debug("PM: Image successfully loaded\n"); else - pr_debug("swsusp: Error %d resuming\n", error); + pr_debug("PM: Error %d resuming\n", error); return error; } @@ -611,13 +612,13 @@ int swsusp_check(void) if (error) blkdev_put(resume_bdev); else - pr_debug("swsusp: Signature found, resuming\n"); + pr_debug("PM: Signature found, resuming\n"); } else { error = PTR_ERR(resume_bdev); } if (error) - pr_debug("swsusp: Error %d check for resume file\n", error); + pr_debug("PM: Error %d checking image file\n", error); return error; } @@ -629,7 +630,7 @@ int swsusp_check(void) void swsusp_close(void) { if (IS_ERR(resume_bdev)) { - pr_debug("swsusp: block device not initialised\n"); + pr_debug("PM: Image device not initialised\n"); return; } diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c index e1722d3..023ff2a 100644 --- a/kernel/power/swsusp.c +++ b/kernel/power/swsusp.c @@ -64,14 +64,6 @@ unsigned long image_size = 500 * 1024 * 1024; int in_suspend __nosavedata = 0; -#ifdef CONFIG_HIGHMEM -unsigned int count_highmem_pages(void); -int restore_highmem(void); -#else -static inline int restore_highmem(void) { return 0; } -static inline unsigned int count_highmem_pages(void) { return 0; } -#endif - /** * The following functions are used for tracing the allocated * swap pages, so that they can be freed in case of an error. @@ -196,7 +188,8 @@ void swsusp_show_speed(struct timeval *start, struct timeval *stop, centisecs = 1; /* avoid div-by-zero */ k = nr_pages * (PAGE_SIZE / 1024); kps = (k * 100) / centisecs; - printk("%s %d kbytes in %d.%02d seconds (%d.%02d MB/s)\n", msg, k, + printk(KERN_INFO "PM: %s %d kbytes in %d.%02d seconds (%d.%02d MB/s)\n", + msg, k, centisecs / 100, centisecs % 100, kps / 1000, (kps % 1000) / 10); } @@ -227,7 +220,7 @@ int swsusp_shrink_memory(void) char *p = "-\\|/"; struct timeval start, stop; - printk("Shrinking memory... "); + printk(KERN_INFO "PM: Shrinking memory... "); do_gettimeofday(&start); do { long size, highmem_size; @@ -269,38 +262,3 @@ int swsusp_shrink_memory(void) return 0; } - -int swsusp_resume(void) -{ - int error; - - local_irq_disable(); - /* NOTE: device_power_down() is just a suspend() with irqs off; - * it has no special "power things down" semantics - */ - if (device_power_down(PMSG_PRETHAW)) - printk(KERN_ERR "Some devices failed to power down, very bad\n"); - /* We'll ignore saved state, but this gets preempt count (etc) right */ - save_processor_state(); - error = restore_highmem(); - if (!error) { - error = swsusp_arch_resume(); - /* The code below is only ever reached in case of a failure. - * Otherwise execution continues at place where - * swsusp_arch_suspend() was called - */ - BUG_ON(!error); - /* This call to restore_highmem() undos the previous one */ - restore_highmem(); - } - /* The only reason why swsusp_arch_resume() can fail is memory being - * very tight, so we have to free it as soon as we can to avoid - * subsequent failures - */ - swsusp_free(); - restore_processor_state(); - touch_softlockup_watchdog(); - device_power_up(); - local_irq_enable(); - return error; -} diff --git a/kernel/power/user.c b/kernel/power/user.c index 5bd321b..f5512cb 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -28,6 +28,29 @@ #include "power.h" +/* + * NOTE: The SNAPSHOT_SET_SWAP_FILE and SNAPSHOT_PMOPS ioctls are obsolete and + * will be removed in the future. They are only preserved here for + * compatibility with existing userland utilities. + */ +#define SNAPSHOT_SET_SWAP_FILE _IOW(SNAPSHOT_IOC_MAGIC, 10, unsigned int) +#define SNAPSHOT_PMOPS _IOW(SNAPSHOT_IOC_MAGIC, 12, unsigned int) + +#define PMOPS_PREPARE 1 +#define PMOPS_ENTER 2 +#define PMOPS_FINISH 3 + +/* + * NOTE: The following ioctl definitions are wrong and have been replaced with + * correct ones. They are only preserved here for compatibility with existing + * userland utilities and will be removed in the future. + */ +#define SNAPSHOT_ATOMIC_SNAPSHOT _IOW(SNAPSHOT_IOC_MAGIC, 3, void *) +#define SNAPSHOT_SET_IMAGE_SIZE _IOW(SNAPSHOT_IOC_MAGIC, 6, unsigned long) +#define SNAPSHOT_AVAIL_SWAP _IOR(SNAPSHOT_IOC_MAGIC, 7, void *) +#define SNAPSHOT_GET_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 8, void *) + + #define SNAPSHOT_MINOR 231 static struct snapshot_data { @@ -36,7 +59,7 @@ static struct snapshot_data { int mode; char frozen; char ready; - char platform_suspend; + char platform_support; } snapshot_state; atomic_t snapshot_device_available = ATOMIC_INIT(1); @@ -44,6 +67,7 @@ atomic_t snapshot_device_available = ATOMIC_INIT(1); static int snapshot_open(struct inode *inode, struct file *filp) { struct snapshot_data *data; + int error; if (!atomic_add_unless(&snapshot_device_available, -1, 0)) return -EBUSY; @@ -64,13 +88,23 @@ static int snapshot_open(struct inode *inode, struct file *filp) data->swap = swsusp_resume_device ? swap_type_of(swsusp_resume_device, 0, NULL) : -1; data->mode = O_RDONLY; + error = pm_notifier_call_chain(PM_RESTORE_PREPARE); + if (error) + pm_notifier_call_chain(PM_POST_RESTORE); } else { data->swap = -1; data->mode = O_WRONLY; + error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE); + if (error) + pm_notifier_call_chain(PM_POST_HIBERNATION); + } + if (error) { + atomic_inc(&snapshot_device_available); + return error; } data->frozen = 0; data->ready = 0; - data->platform_suspend = 0; + data->platform_support = 0; return 0; } @@ -88,6 +122,8 @@ static int snapshot_release(struct inode *inode, struct file *filp) thaw_processes(); mutex_unlock(&pm_mutex); } + pm_notifier_call_chain(data->mode == O_WRONLY ? + PM_POST_HIBERNATION : PM_POST_RESTORE); atomic_inc(&snapshot_device_available); return 0; } @@ -133,7 +169,7 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, { int error = 0; struct snapshot_data *data; - loff_t avail; + loff_t size; sector_t offset; if (_IOC_TYPE(cmd) != SNAPSHOT_IOC_MAGIC) @@ -151,18 +187,13 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, if (data->frozen) break; mutex_lock(&pm_mutex); - error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE); - if (!error) { - printk("Syncing filesystems ... "); - sys_sync(); - printk("done.\n"); - - error = freeze_processes(); - if (error) - thaw_processes(); - } + printk("Syncing filesystems ... "); + sys_sync(); + printk("done.\n"); + + error = freeze_processes(); if (error) - pm_notifier_call_chain(PM_POST_HIBERNATION); + thaw_processes(); mutex_unlock(&pm_mutex); if (!error) data->frozen = 1; @@ -173,19 +204,19 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, break; mutex_lock(&pm_mutex); thaw_processes(); - pm_notifier_call_chain(PM_POST_HIBERNATION); mutex_unlock(&pm_mutex); data->frozen = 0; break; + case SNAPSHOT_CREATE_IMAGE: case SNAPSHOT_ATOMIC_SNAPSHOT: if (data->mode != O_RDONLY || !data->frozen || data->ready) { error = -EPERM; break; } - error = hibernation_snapshot(data->platform_suspend); + error = hibernation_snapshot(data->platform_support); if (!error) - error = put_user(in_suspend, (unsigned int __user *)arg); + error = put_user(in_suspend, (int __user *)arg); if (!error) data->ready = 1; break; @@ -197,7 +228,7 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, error = -EPERM; break; } - error = hibernation_restore(data->platform_suspend); + error = hibernation_restore(data->platform_support); break; case SNAPSHOT_FREE: @@ -206,16 +237,29 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, data->ready = 0; break; + case SNAPSHOT_PREF_IMAGE_SIZE: case SNAPSHOT_SET_IMAGE_SIZE: image_size = arg; break; + case SNAPSHOT_GET_IMAGE_SIZE: + if (!data->ready) { + error = -ENODATA; + break; + } + size = snapshot_get_image_size(); + size <<= PAGE_SHIFT; + error = put_user(size, (loff_t __user *)arg); + break; + + case SNAPSHOT_AVAIL_SWAP_SIZE: case SNAPSHOT_AVAIL_SWAP: - avail = count_swap_pages(data->swap, 1); - avail <<= PAGE_SHIFT; - error = put_user(avail, (loff_t __user *)arg); + size = count_swap_pages(data->swap, 1); + size <<= PAGE_SHIFT; + error = put_user(size, (loff_t __user *)arg); break; + case SNAPSHOT_ALLOC_SWAP_PAGE: case SNAPSHOT_GET_SWAP_PAGE: if (data->swap < 0 || data->swap >= MAX_SWAPFILES) { error = -ENODEV; @@ -224,7 +268,7 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, offset = alloc_swapdev_block(data->swap); if (offset) { offset <<= PAGE_SHIFT; - error = put_user(offset, (sector_t __user *)arg); + error = put_user(offset, (loff_t __user *)arg); } else { error = -ENOSPC; } @@ -238,7 +282,7 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, free_all_swap_pages(data->swap); break; - case SNAPSHOT_SET_SWAP_FILE: + case SNAPSHOT_SET_SWAP_FILE: /* This ioctl is deprecated */ if (!swsusp_swap_in_use()) { /* * User space encodes device types as two-byte values, @@ -275,26 +319,33 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, mutex_unlock(&pm_mutex); break; - case SNAPSHOT_PMOPS: + case SNAPSHOT_PLATFORM_SUPPORT: + data->platform_support = !!arg; + break; + + case SNAPSHOT_POWER_OFF: + if (data->platform_support) + error = hibernation_platform_enter(); + break; + + case SNAPSHOT_PMOPS: /* This ioctl is deprecated */ error = -EINVAL; switch (arg) { case PMOPS_PREPARE: - data->platform_suspend = 1; + data->platform_support = 1; error = 0; break; case PMOPS_ENTER: - if (data->platform_suspend) + if (data->platform_support) error = hibernation_platform_enter(); - break; case PMOPS_FINISH: - if (data->platform_suspend) + if (data->platform_support) error = 0; - break; default: