GIT 5928460c5ca4f1f03bf6d2f0e7ffb29bb9b371f7 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6.git#test 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 61fd47e0c84764f49b4e52bfd8170fac52636f00 Author: Shaohua Li Date: Sat Nov 17 01:05:28 2007 -0500 ACPI: fix two IRQ8 issues in IOAPIC mode Use mp_irqs[] to get PNP device's interrupt polarity and trigger. There are two reasons to do this: 1. BIOS bug for PNP interrupt 2. BIOS explictly does override mp_irqs[] should cover all the cases. http://bugzilla.kernel.org/show_bug.cgi?id=5243 http://bugzilla.kernel.org/show_bug.cgi?id=7679 http://bugzilla.kernel.org/show_bug.cgi?id=9153 [lenb: fixed !IOAPIC and 64-bit !SMP builds] Signed-off-by: Shaohua Li Signed-off-by: Len Brown commit abd75f7ef601a04e6feb5728b7ffd7f8a2f8c79f 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 66a9be70e09795ed17300a4e1d7a2207d9ea9092 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 bb6274216fd42b719e9b03ade9d951243dbd7f23 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 9390bb8c09d67155bade62fabce77fba8ae343a4 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 b6bb6c047d0417c154269a569f49906202226267 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 bfe0e90eb67331f16a3cf916108bb4a0d8c206d7 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 21371c7ae87dbd04f6a82e3297dd08771a595b3d 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 f697a8f9de26567b5be5f0f8a53da628075896cd 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 0f93bf47dca056a62e5118c9f6eeb6df4cef1c8a 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 b8f5d08a2d518e2147e2132e98a4535860128d29 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 d79a56cf4e5fa2f0a9aec30fa33ce243f99c09ef 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 1d56435859c7192ee77aae801a0cde40542431ee 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 0064804d568a0e30c8d490587049c4f874e67903 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 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 4fdb2a05ef5703553fdd28f1b96ebdd79f173657 Author: Joe Perches Date: Mon Nov 19 17:48:02 2007 -0800 ACPI: Add missing spaces to printk format Signed-off-by: Joe Perches Signed-off-by: Len Brown commit ddc081a19585c8ba5aad437779950c2ef215360a Author: Venkatesh Pallipadi Date: Mon Nov 19 21:43:22 2007 -0500 cpuidle: fix HP nx6125 regression Fix for http://bugzilla.kernel.org/show_bug.cgi?id=9355 cpuidle always used to fallback to C2 if there is some bm activity while entering C3. But, presence of C2 is not always guaranteed. Change cpuidle algorithm to detect a safe_state to fallback in case of bm_activity and use that state instead of C2. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown commit 5062911830a66df0c0ad28c387a8c0623cb0d28c Author: Venkatesh Pallipadi Date: Mon Nov 19 19:49:00 2007 -0500 cpuidle: add sched_clock_idle_[sleep|wakeup]_event() hooks Port 2aa44d0567ed21b47b87d68819415d48194cb923 (sched: sched_clock_idle_[sleep|wakeup]_event()) to cpuidle. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown commit c9c860e5349ef62cd9226694b3aa625ef66f504e Author: Venkatesh Pallipadi Date: Mon Nov 19 19:48:00 2007 -0500 cpuidle: fix C3 for no bus-master control case Port 18eab8550397f1f3d4b8b2c5257c88dae25d58ed (Enable C3 even when PM2_control is zero) to cpuidle. Without this patch, some systems will notice a regression when enabling CPU_IDLE -- C3 would no longer be available. Signed-off-by: Venkatesh Pallipadi Signed-off-by: Len Brown commit 59f91ff11e594913a5b3c03a4707fdf02338c8df Author: Henrique de Moraes Holschuh Date: Sun Nov 18 09:18:29 2007 -0200 ACPI: thinkpad-acpi: fix oops when a module parameter has no value set_ibm_param() could OOPS with a NULL pointer derreference if one did not give any values for a module parameter it handles. This would, of course, cause all sort of trouble for future modprobing and require a reboot to clean up properly. Fix it by returning -EINVAL if no values are given for the parameter, and also avoid any nastyness from BUG_ON while at it. How to reproduce: modprobe thinkpad-acpi brightness Signed-off-by: Henrique de Moraes Holschuh Tested-by: Mike Kershaw 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 f0714d20234062bd0a8f49a6b32f7d1d7f3c2943 Author: Len Brown Date: Mon Nov 19 12:23:59 2007 -0500 Revert "Fix very high interrupt rate for IRQ8 (rtc) unless pnpacpi=off" This reverts commit 9cd8047b463f213c294f756119ac353312e7a152. commit 5870a8cd23181703cc76f88f630372f8602c7648 Author: Alexey Starikovskiy Date: Thu Nov 15 21:52:47 2007 +0300 ACPI: EC: Don't init EC early if it has no _INI Option to init EC early inserted to handle #8598 ASUS problem, introduced several others. EC driver in this particular case has fake _INI method, not present on other machines, which don't need or break from this workaround, so lets use its presence as a flag for early init. http://bugzilla.kernel.org/show_bug.cgi?id=9262 http://bugzilla.kernel.org/show_bug.cgi?id=8598 https://bugzilla.novell.com/show_bug.cgi?id=334806 Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 65ea6520375cc09d19ecb46f03ab7ef70bcf06dd Author: Len Brown Date: Mon Nov 19 11:22:44 2007 -0500 Revert "acpi: make ACPI_PROCFS default to y" This reverts commit cbff2fbf55c21f50298b1aef1263b11bf510e35f. commit 3539a901d60ae84f8b0748cd26c1c263c2b3ef5f Author: Len Brown Date: Mon Nov 19 11:22:35 2007 -0500 Revert "ACPI: add documentation for deprecated /proc/acpi/battery in ACPI_PROCFS" This reverts commit 6e800af233e0bdf108efb7bd23c11ea6fa34cdeb. commit fdcedbba2f98c94bfbac9f6e712ab765f997b8dc Author: Alexey Starikovskiy Date: Mon Nov 19 16:33:45 2007 +0300 ACPI: Split out control for /proc/acpi entries from battery, ac, and sbs. Introduce new ACPI_PROCFS_POWER (default Yes) config option and move procfs code in battery, ac, and sbs drivers under it. This is done to allow ACPI_PROCFS to be default No. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit c88c5786d3df51ccfa4e2d111fc9c8fc0f5b2797 Author: Danny Baumann Date: Fri Nov 2 13:47:53 2007 +0100 ACPI: Video: Increase buffer size for writes to brightness proc file. In order to be able to write the value "100" to /proc/acpi/video/.../brightness, we have to allocate 5 bytes: 4 characters will be written (1, 0, 0 plus null byte), and 1 byte should be buffer for a terminating NULL character. http://bugzilla.kernel.org/show_bug.cgi?id=9278 Signed-off-by: Danny Baumann Acked-by: Zhang Rui Signed-off-by: Len Brown commit f2d68935ba08cf80f151bbdb5628381184e4a498 Author: Alexey Starikovskiy Date: Mon Nov 19 01:37:03 2007 +0300 ACPI: EC: Workaround for optimized controllers Some controllers fail to send confirmation GPE after address write. Detect this and don't expect such confirmation in future. This is a generalization of previous workaround (66c5f4e7367b0085652931b2f3366de29e7ff5ec), which did only read address. http://bugzilla.kernel.org/show_bug.cgi?id=9327 Signed-off-by: Alexey Starikovskiy Tested-by: Romano Giannetti Signed-off-by: Len Brown commit 037cbc63fd83162a8ee0c69680207ce4609adbea Author: Jeff Garzik Date: Sun Nov 18 00:32:31 2007 +0300 ACPI: SBS: Fix retval warning drivers/acpi/sbs.c: In function acpi_battery_add: drivers/acpi/sbs.c:811: warning: ignoring return value of device_create_file, declared with attribute warn_unused_result Additional cleanups: * use struct acpi_battery in acpi_battery_remove() to clean up function calls, just like acpi_battery_add() already does. * put braces around unregister call, as it depends on dev being not NULL. * remove unneeded braces Signed-off-by: Jeff Garzik Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown commit f79f06ab9f86d7203006d2ec8992ac80df36a34e Author: Zhao Yakui Date: Thu Nov 15 17:06:36 2007 +0800 ACPI: Enable MSR (FixedHW) support for T-States Add throttling control via MSR when T-states uses the FixHW Control Status registers. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 0ac3c571315a53c14d2733564f14ebdb911fe903 Author: Zhao Yakui Date: Thu Nov 15 17:05:46 2007 +0800 ACPI: Get throttling info from BIOS only after evaluating _PDC Previously _PDC was evaluated later, and thus we'd not get the chance to tell the BIOS that we can suport FixedHW registers (MSRs) and the BIOS would always ask us to use System I/O access for throttling. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 9bcb27217344c2c1389db3983a436e19484c2f50 Author: Zhao Yakui Date: Thu Nov 15 17:05:05 2007 +0800 ACPI: Use _TSS for throttling control, when present. Add error checks. _TSS was erroneously ignored, in favor of the FADT. When TSS is used, the access width is included in the PTC control/status register. So it is unnecessary that the access bit width is multiplied by 8. At the same time the bit_offset should be considered for system I/O Access. It should be checked the bit_width and bit_offset of PTC regsiter in order to avoid the failure of system I/O access. It means that bit_width plus bit_offset can't be greater than 32. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 0753f6e0a3d9568fb6b8e34753b944d9f8eed05b Author: Zhao Yakui Date: Thu Nov 15 17:03:46 2007 +0800 ACPI: throttle: Change internal APIs better handle _PTC Change the function interface for throttling control via PTC. The following functions are concerned: acpi_read_throttling_status() acpi_write_throttling_state() acpi_get_throttling_value() acpi_get_throttling_state() Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 22cc50199d0616f7b002563a0e9117ba479356e1 Author: Zhao Yakui Date: Thu Nov 15 17:02:03 2007 +0800 ACPI: If _TSS exists, do not access FADT.duty_width Factor out legacy FADT.duty_width code and run it only in the non _TSS case. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 49fbabf56dc715bbb51e59742e82ba762790aac0 Author: Zhao Yakui Date: Thu Nov 15 17:01:06 2007 +0800 ACPI: Handle I/O access width requestst that are not a multiple of 8 bits. We've run into BIOS that hand us 4-bit access width requests for T-state control when the code expected only multipls of 8-bits. Round up. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit ef54d5ad2f58f899be6419fd1090cdeb2439851a Author: Zhao Yakui Date: Thu Nov 15 16:59:30 2007 +0800 ACPI: Enforce T-state limit changes immediately When a T-state limit change notification is received, Linux must evaluate _TPC and change its current T-state immediately to comply with the new limit. Previously, Linux would notice the new limit only upon the next throttling change. Signed-off-by: Zhao Yakui Signed-off-by: Li Shaohua Signed-off-by: Len Brown commit 55b8d50c1a7b2d53eddaa3114dc55b0ed00df0f3 Author: Adrian Bunk Date: Fri Nov 9 07:02:11 2007 +0100 x86: acpi_pciprobe_dmi_table[] must be __devinitdata This patch fixes the following section mismatches with CONFIG_HOTPLUG=n: <-- snip --> ... WARNING: vmlinux.o(.data+0x23640): Section mismatch: reference to .init.text.20:can_skip_ioresource_align (between 'acpi_pciprobe_dmi_table' and 'pcibios_irq_mask') WARNING: vmlinux.o(.data+0x2366c): Section mismatch: reference to .init.text.20:can_skip_ioresource_align (between 'acpi_pciprobe_dmi_table' and 'pcibios_irq_mask') WARNING: vmlinux.o(.data+0x23698): Section mismatch: reference to .init.text.20:can_skip_ioresource_align (between 'acpi_pciprobe_dmi_table' and 'pcibios_irq_mask') ... <-- snip --> Signed-off-by: Adrian Bunk Signed-off-by: Len Brown commit 16ee2db63ef78c387bdda6e0cde3a2051518b6cf Author: Adrian Bunk Date: Fri Nov 9 07:03:13 2007 +0100 x86_64: remove acpi_pci_link_exit() acpi_pci_link_exit() is both unused and empty. Signed-off-by: Adrian Bunk 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 commit a4f0c2767e9c55123d7dad7176554e9d6e6056bc Author: Len Brown Date: Wed Nov 14 12:49:13 2007 -0500 ACPI: video - delete stray run-time printk printk("video bus notify\n"); Acked-by: Zhang Rui Signed-off-by: Len Brown commit bbac81f5487175e4bd5602a80c17689d8f82a63e Author: Dmitry Torokhov Date: Mon Nov 5 11:43:32 2007 -0500 ACPI: video - convert semaphore to a mutex Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit ff102ea99099c36250e93a87a9794b5233801020 Author: Dmitry Torokhov Date: Mon Nov 5 11:43:31 2007 -0500 ACPI: video - remove unsafe uses of list_for_each_safe() list_for_each_safe() only protects list from list alterations performed by the same thread. One still needs to implement proper locking when list is being accessed from several threads. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit f51e83916a0a022d3d0ea39ae2f877c703032923 Author: Dmitry Torokhov Date: Mon Nov 5 11:43:30 2007 -0500 ACPI: video - add missing input_free_device() If input_register_device() fails input_free_device() must be called to release memory allocated for the device. Also consolidate error handling in acpi_bus_video_add() and handle input_allocate_device() failures. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit 91c05c667b2d8e43e0bbc5f269bf45d4821001d6 Author: Dmitry Torokhov Date: Mon Nov 5 11:43:29 2007 -0500 ACPI: video - fit input device into sysfs tree Properly set up parent on input device registered by the video driver. Signed-off-by: Dmitry Torokhov Acked-by: Zhang Rui Signed-off-by: Len Brown commit 4c41d3ad6544f1c9aec37c441af04f5d0ad3a731 Author: Roland Dreier Date: Wed Nov 7 15:09:09 2007 -0800 ACPI: Always return valid 'status' from acpi_battery_get_property() If a battery is at a critical charge level and not being charged or discharged, then the ACPI _BST method will return a state of 4, and the current acpi_battery_get_property() code will not set any property value for POWER_SUPPLY_PROP_STATUS. This will cause an oops in power_supply_show_property() when it reads off the end of the status_text array. This actually was causing a 100% reproducible crash on boot on my laptop with two batteries, when one battery was completely drained and the laptop was not plugged in. Fix this by making sure acpi_battery_get_property() returns POWER_SUPPLY_STATUS_UNKNOWN for any battery state it doesn't already handle explicitly. There doesn't seem to be any status enum value defined that makes more sense than 'unknown' for a battery at a critical charge level. Signed-off-by: Roland Dreier Acked-by: Alexey Starikovskiy Signed-off-by: Len Brown commit 4273af8d08c823d5898a2b1c2d0f25b4a8b9eaee Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:25 2007 -0200 ACPI: thinkpad-acpi: fix brightness_set error paths The code calling brightness_set() can't handle EINTR/ERESTARTSYS well, nor is it checking brightness_set() return status properly. Fix it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit fc589a3ce5f38db6239c147da4f9172a25575ecc Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:24 2007 -0200 ACPI: thinkpad-acpi: allow for syscall restart in sysfs handlers Map an mutex_lock_interruptible() error return into ERESTARTSYS, as the only possible error from mutex_lock_interruptible is EINTR, and that will only happen if signal_pending() causes the mutex lock attempt to abort. This still allows signals to be delivered ASAP, which is much nicer than just doing mutex_lock, and still shadows userspace from EINTR when SA_RESTART is active. Problem reported by Peter Jordan. Signed-off-by: Henrique de Moraes Holschuh Cc: Jean Delvare Cc: Peter Jordan Cc: Richard Neill Signed-off-by: Len Brown commit b856f5b8c022b75bb0504a8c1ce16a5f1656e08b Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:23 2007 -0200 ACPI: thinkpad-acpi: bump up version to 0.17 The lm-sensors 3.0.0/libsensors4 compatibility changes are reason enough to bump up the version string. Do it. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit e11e211a0b21bbb625fac2056bdb54dd02020556 Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:22 2007 -0200 ACPI: thinkpad-acpi: prefer standard ACPI backlight level control Newer Lenovo BIOSes support the standard ACPI backlight brightness interface (_BCM, _BQC, _BCL). It should be used instead of the native thinkpad backlight brightness control interface when possible. This patch disables the native brightness support in the driver by default when we detect that the standard ACPI interface is available. The local admin can still enable it using the module parameter "brightness_enable". Note that we need to detect the standard ACPI backlight interface only in boxes for which we would load the native backlight interface in the first place, and that no ThinkPad BIOS has _BCL but misses the other methods, so the detection routines can be really simple. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit 87cc537a54fc017d998cf603f5fab9ca4a85d668 Author: Henrique de Moraes Holschuh Date: Tue Oct 30 18:02:07 2007 -0200 ACPI: thinkpad-acpi: add brightness_force parameter Add a "brightness_enable" module parameter that allows the local admin to force the backlight support to not be enabled. It can also be used to force the backlight support to be enabled, but that is currently a no-op as the backlight support is enabled by default when available. This will be changed by a different patch. Signed-off-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown commit a3f104c02ab842574e699186cf953551aafe2ca9 Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:20 2007 -0200 ACPI: thinkpad-acpi: support 16 levels of brightness (v3) Lenovo ThinkPads often have 16 brightness levels in EC, and not just eight levels like older ThinkPads. They also have standard ACPI backlight brightness control. We detect the number of brightness levels by the presence of a BCLL package with 16 entries. If BCLL is not there, we assume eight levels (Z6*). If it is there, but it doesn't have 16 entries, we assume eight levels (T60). Otherwise we assume sixteen levels (T61, X61, etc). We don't use _BCL because it can have side-effects in thinkpads. Thanks to Thomas Renninger for notifying me of this potential problem. Using the standard ACPI backlight brightness control *instead* of the native thinkpad backlight control is a better idea, though. A different patch will take care of this. Signed-off-by: Henrique de Moraes Holschuh Cc: Thomas Renninger Signed-off-by: Len Brown commit e927c08da53e5c87ca07f7a828d4a0048e7bacf0 Author: Henrique de Moraes Holschuh Date: Tue Oct 30 17:46:19 2007 -0200 ACPI: thinkpad-acpi: revert keymap changes Revert commit fba956c46a72f9e7503fd464ffee43c632307e31, "Map volume and brightness events on thinkpads". That commit made some modifications to the default keymaps that cause bad behaviour on all IBM ThinkPads if HAL doesn't know to change them into passive (on-screen-display only) events. The proper solution for IBM ThinkPads is to use the _NOTIFY version of the key codes for the IBM default map (which are not available in mainline yet), and for the Lenovo keymap, it will take some studying of the various DSDTs and testing to know the best path (which I will do shortly). For more data, refer to: http://thread.gmane.org/gmane.linux.kernel/591037/focus=591045 Signed-off-by: Henrique de Moraes Holschuh Cc: Jeremy Katz Signed-off-by: Len Brown commit 4ae8b9db5b925897eca1a1f01a11b61a7af82a1b Author: Adrian Bunk Date: Wed Oct 24 18:24:42 2007 +0200 ACPI: possible cleanups This patch contains the following possible cleanups: - make the following needlessly global code static: - drivers/acpi/battery.c:acpi_battery_present() - drivers/acpi/battery.c:acpi_battery_units() - drivers/acpi/bay.c:dev_attr_eject - drivers/acpi/bay.c:dev_attr_present - drivers/acpi/dock.c:dev_attr_docked - drivers/acpi/dock.c:dev_attr_flags - drivers/acpi/dock.c:dev_attr_uid - drivers/acpi/dock.c:dev_attr_undock - drivers/acpi/pci_bind.c:acpi_pci_unbind() - drivers/acpi/pci_link.c:acpi_link_lock - drivers/acpi/sbs.c:acpi_sbs_callback() - drivers/acpi/sbshc.c:acpi_smbus_transaction() - drivers/acpi/sleep/main.c:acpi_sleep_prepare() - #if 0 the following unused global functions: - drivers/acpi/ec.c:acpi_ec_burst_disable() - drivers/acpi/ec.c:acpi_ec_burst_enable() - drivers/acpi/ec.c:ec_burst_disable() - drivers/acpi/ec.c:ec_burst_enable() - drivers/acpi/numa.c:acpi_unmap_pxm_to_node() - remove the following unused EXPORT_SYMBOL's: - acpi_register_gsi - acpi_unregister_gsi - acpi_strict - acpi_bus_receive_event - register_acpi_bus_type - unregister_acpi_bus_type - acpi_os_printf - acpi_os_sleep - acpi_os_stall - acpi_os_read_pci_configuration - acpi_os_create_semaphore - acpi_os_delete_semaphore - acpi_os_wait_semaphore - acpi_os_signal_semaphore - acpi_os_signal - acpi_pci_irq_enable - acpi_get_pxm Signed-off-by: Adrian Bunk Signed-off-by: Len Brown Signed-off-by: Andrew Morton --- Documentation/00-INDEX | 3 Documentation/acpi/method-tracing.txt | 26 Documentation/power/basic-pm-debugging.txt | 212 - Documentation/power/drivers-testing.txt | 30 Documentation/power/notifiers.txt | 8 Documentation/power/userland-swsusp.txt | 82 Documentation/thinkpad-acpi.txt | 161 arch/ia64/kernel/acpi.c | 4 arch/x86/kernel/acpi/boot.c | 3 arch/x86/kernel/acpi/processor.c | 3 arch/x86/kernel/acpi/sleep_64.c | 3 arch/x86/kernel/io_apic_32.c | 21 arch/x86/kernel/io_apic_64.c | 24 arch/x86/pci/acpi.c | 2 drivers/acpi/Kconfig | 16 drivers/acpi/Makefile | 2 drivers/acpi/ac.c | 20 drivers/acpi/battery.c | 26 drivers/acpi/bay.c | 4 drivers/acpi/bus.c | 1 drivers/acpi/debug.c | 57 drivers/acpi/dock.c | 8 drivers/acpi/ec.c | 51 drivers/acpi/events/evgpe.c | 8 drivers/acpi/glue.c | 4 drivers/acpi/numa.c | 3 drivers/acpi/osl.c | 43 drivers/acpi/pci_bind.c | 102 drivers/acpi/pci_irq.c | 2 drivers/acpi/pci_link.c | 2 drivers/acpi/processor_core.c | 14 drivers/acpi/processor_idle.c | 111 drivers/acpi/processor_throttling.c | 286 + drivers/acpi/sbs.c | 47 drivers/acpi/sbshc.c | 4 drivers/acpi/sleep/main.c | 7 drivers/acpi/sleep/sleep.h | 1 drivers/acpi/tables/tbutils.c | 2 drivers/acpi/video.c | 1214 +++--- drivers/base/power/Makefile | 2 drivers/cpuidle/Kconfig | 4 drivers/misc/Kconfig | 19 drivers/misc/thinkpad_acpi.c | 3637 ++++++++++++------- drivers/misc/thinkpad_acpi.h | 604 --- drivers/pnp/pnpacpi/rsparser.c | 1 include/acpi/acpi_drivers.h | 1 include/acpi/acpi_numa.h | 1 include/acpi/processor.h | 1 include/linux/Kbuild | 1 include/linux/acpi.h | 5 include/linux/cpuidle.h | 1 include/linux/notifier.h | 2 include/linux/suspend.h | 13 include/linux/suspend_ioctls.h | 32 kernel/power/Kconfig | 23 kernel/power/disk.c | 75 kernel/power/main.c | 137 kernel/power/power.h | 71 kernel/power/snapshot.c | 8 kernel/power/swsusp.c | 8 kernel/power/user.c | 109 61 files changed, 4306 insertions(+), 3066 deletions(-) diff -puN Documentation/00-INDEX~git-acpi Documentation/00-INDEX --- a/Documentation/00-INDEX~git-acpi +++ a/Documentation/00-INDEX @@ -14,6 +14,7 @@ Following translations are available on - 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 -puN /dev/null Documentation/acpi/method-tracing.txt --- /dev/null +++ a/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 -puN Documentation/power/basic-pm-debugging.txt~git-acpi Documentation/power/basic-pm-debugging.txt --- a/Documentation/power/basic-pm-debugging.txt~git-acpi +++ a/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 -a) Test mode of STD +devices +- test the freezing of processes and suspending of devices -To verify if there are any drivers that cause problems you can run the STD -in the test mode: +platform +- test the freezing of processes, suspending of devices and platform + global control methods(*) -# echo test > /sys/power/disk +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" + +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. +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 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". +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 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. +carry out STR testing using the facility 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. +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 -puN Documentation/power/drivers-testing.txt~git-acpi Documentation/power/drivers-testing.txt --- a/Documentation/power/drivers-testing.txt~git-acpi +++ a/Documentation/power/drivers-testing.txt @@ -6,9 +6,9 @@ Testing suspend and resume support in de 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 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 -puN Documentation/power/notifiers.txt~git-acpi Documentation/power/notifiers.txt --- a/Documentation/power/notifiers.txt~git-acpi +++ a/Documentation/power/notifiers.txt @@ -28,6 +28,14 @@ PM_POST_HIBERNATION The system memory st 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 -puN Documentation/power/userland-swsusp.txt~git-acpi Documentation/power/userland-swsusp.txt --- a/Documentation/power/userland-swsusp.txt~git-acpi +++ a/Documentation/power/userland-swsusp.txt @@ -14,7 +14,7 @@ are going to develop your own suspend/re 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 sy 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 t 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 f 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 u 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 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 -puN Documentation/thinkpad-acpi.txt~git-acpi Documentation/thinkpad-acpi.txt --- a/Documentation/thinkpad-acpi.txt~git-acpi +++ a/Documentation/thinkpad-acpi.txt @@ -1,7 +1,7 @@ ThinkPad ACPI Extras Driver - Version 0.16 - August 2nd, 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 @@ -923,19 +993,34 @@ sysfs backlight device "thinkpad_screen" This feature allows software control of the LCD brightness on ThinkPad models which don't have a hardware brightness slider. -It has some limitations: the LCD backlight cannot be actually turned on or off -by this interface, and in many ThinkPad models, the "dim while on battery" -functionality will be enabled by the BIOS when this interface is used, and -cannot be controlled. - -The backlight control has eight levels, ranging from 0 to 7. Some of the -levels may not be distinct. - -There are two interfaces to the firmware for brightness control, EC and CMOS. -To select which one should be used, use the brightness_mode module parameter: -brightness_mode=1 selects EC mode, brightness_mode=2 selects CMOS mode, -brightness_mode=3 selects both EC and CMOS. The driver tries to autodetect -which interface to use. +It has some limitations: the LCD backlight cannot be actually turned on or +off by this interface, and in many ThinkPad models, the "dim while on +battery" functionality will be enabled by the BIOS when this interface is +used, and cannot be controlled. + +On IBM (and some of the earlier Lenovo) ThinkPads, the backlight control +has eight brightness levels, ranging from 0 to 7. Some of the levels +may not be distinct. Later Lenovo models that implement the ACPI +display backlight brightness control methods have 16 levels, ranging +from 0 to 15. + +There are two interfaces to the firmware for direct brightness control, +EC and CMOS. To select which one should be used, use the +brightness_mode module parameter: brightness_mode=1 selects EC mode, +brightness_mode=2 selects CMOS mode, brightness_mode=3 selects both EC +and CMOS. The driver tries to autodetect which interface to use. + +When display backlight brightness controls are available through the +standard ACPI interface, it is best to use it instead of this direct +ThinkPad-specific interface. The driver will disable its native +backlight brightness control interface if it detects that the standard +ACPI interface is available in the ThinkPad. + +The brightness_enable module parameter can be used to control whether +the LCD brightness control feature will be enabled when available. +brightness_enable=0 forces it to be disabled. brightness_enable=1 +forces it to be enabled when available, even if the standard ACPI +interface is also available. Procfs notes: @@ -947,11 +1032,11 @@ Procfs notes: Sysfs notes: -The interface is implemented through the backlight sysfs class, which is poorly -documented at this time. +The interface is implemented through the backlight sysfs class, which is +poorly documented at this time. -Locate the thinkpad_screen device under /sys/class/backlight, and inside it -there will be the following attributes: +Locate the thinkpad_screen device under /sys/class/backlight, and inside +it there will be the following attributes: max_brightness: Reads the maximum brightness the hardware can be set to. @@ -961,17 +1046,19 @@ there will be the following attributes: Reads what brightness the screen is set to at this instant. brightness: - Writes request the driver to change brightness to the given - value. Reads will tell you what brightness the driver is trying - to set the display to when "power" is set to zero and the display - has not been dimmed by a kernel power management event. + Writes request the driver to change brightness to the + given value. Reads will tell you what brightness the + driver is trying to set the display to when "power" is set + to zero and the display has not been dimmed by a kernel + power management event. power: - power management mode, where 0 is "display on", and 1 to 3 will - dim the display backlight to brightness level 0 because - thinkpad-acpi cannot really turn the backlight off. Kernel - power management events can temporarily increase the current - power management level, i.e. they can dim the display. + power management mode, where 0 is "display on", and 1 to 3 + will dim the display backlight to brightness level 0 + because thinkpad-acpi cannot really turn the backlight + off. Kernel power management events can temporarily + increase the current power management level, i.e. they can + dim the display. Volume control -- /proc/acpi/ibm/volume @@ -1246,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 -puN arch/ia64/kernel/acpi.c~git-acpi arch/ia64/kernel/acpi.c --- a/arch/ia64/kernel/acpi.c~git-acpi +++ a/arch/ia64/kernel/acpi.c @@ -601,8 +601,6 @@ int acpi_register_gsi(u32 gsi, int trigg IOSAPIC_LEVEL); } -EXPORT_SYMBOL(acpi_register_gsi); - void acpi_unregister_gsi(u32 gsi) { if (acpi_irq_model == ACPI_IRQ_MODEL_PLATFORM) @@ -611,8 +609,6 @@ void acpi_unregister_gsi(u32 gsi) iosapic_unregister_intr(gsi); } -EXPORT_SYMBOL(acpi_unregister_gsi); - static int __init acpi_parse_fadt(struct acpi_table_header *table) { struct acpi_table_header *fadt_header; diff -puN arch/x86/kernel/acpi/boot.c~git-acpi arch/x86/kernel/acpi/boot.c --- a/arch/x86/kernel/acpi/boot.c~git-acpi +++ a/arch/x86/kernel/acpi/boot.c @@ -78,7 +78,6 @@ int acpi_ht __initdata = 1; /* enable HT int acpi_lapic; int acpi_ioapic; int acpi_strict; -EXPORT_SYMBOL(acpi_strict); u8 acpi_sci_flags __initdata; int acpi_sci_override_gsi __initdata; @@ -490,8 +489,6 @@ int acpi_register_gsi(u32 gsi, int trigg return irq; } -EXPORT_SYMBOL(acpi_register_gsi); - /* * ACPI based hotplug support for CPU */ diff -puN arch/x86/kernel/acpi/processor.c~git-acpi arch/x86/kernel/acpi/processor.c --- a/arch/x86/kernel/acpi/processor.c~git-acpi +++ a/arch/x86/kernel/acpi/processor.c @@ -49,6 +49,9 @@ static void init_intel_pdc(struct acpi_p if (cpu_has(c, X86_FEATURE_EST)) buf[2] |= ACPI_PDC_EST_CAPABILITY_SWSMP; + if (cpu_has(c, X86_FEATURE_ACPI)) + buf[2] |= ACPI_PDC_T_FFH; + obj->type = ACPI_TYPE_BUFFER; obj->buffer.length = 12; obj->buffer.pointer = (u8 *) buf; diff -puN arch/x86/kernel/acpi/sleep_64.c~git-acpi arch/x86/kernel/acpi/sleep_64.c --- a/arch/x86/kernel/acpi/sleep_64.c~git-acpi +++ a/arch/x86/kernel/acpi/sleep_64.c @@ -115,6 +115,3 @@ static int __init acpi_sleep_setup(char __setup("acpi_sleep=", acpi_sleep_setup); -void acpi_pci_link_exit(void) -{ -} diff -puN arch/x86/kernel/io_apic_32.c~git-acpi arch/x86/kernel/io_apic_32.c --- a/arch/x86/kernel/io_apic_32.c~git-acpi +++ a/arch/x86/kernel/io_apic_32.c @@ -962,7 +962,7 @@ static int EISA_ELCR(unsigned int irq) #define default_MCA_trigger(idx) (1) #define default_MCA_polarity(idx) (0) -static int __init MPBIOS_polarity(int idx) +static int MPBIOS_polarity(int idx) { int bus = mp_irqs[idx].mpc_srcbus; int polarity; @@ -2830,6 +2830,25 @@ int io_apic_set_pci_routing (int ioapic, return 0; } +int acpi_get_override_irq(int bus_irq, int *trigger, int *polarity) +{ + int i; + + if (skip_ioapic_setup) + return -1; + + for (i = 0; i < mp_irq_entries; i++) + if (mp_irqs[i].mpc_irqtype == mp_INT && + mp_irqs[i].mpc_srcbusirq == bus_irq) + break; + if (i >= mp_irq_entries) + return -1; + + *trigger = irq_trigger(i); + *polarity = irq_polarity(i); + return 0; +} + #endif /* CONFIG_ACPI */ static int __init parse_disable_timer_pin_1(char *arg) diff -puN arch/x86/kernel/io_apic_64.c~git-acpi arch/x86/kernel/io_apic_64.c --- a/arch/x86/kernel/io_apic_64.c~git-acpi +++ a/arch/x86/kernel/io_apic_64.c @@ -546,7 +546,7 @@ int IO_APIC_get_PCI_irq_vector(int bus, #define default_PCI_trigger(idx) (1) #define default_PCI_polarity(idx) (1) -static int __init MPBIOS_polarity(int idx) +static int MPBIOS_polarity(int idx) { int bus = mp_irqs[idx].mpc_srcbus; int polarity; @@ -2222,8 +2222,27 @@ int io_apic_set_pci_routing (int ioapic, return 0; } -#endif /* CONFIG_ACPI */ +int acpi_get_override_irq(int bus_irq, int *trigger, int *polarity) +{ + int i; + + if (skip_ioapic_setup) + return -1; + + for (i = 0; i < mp_irq_entries; i++) + if (mp_irqs[i].mpc_irqtype == mp_INT && + mp_irqs[i].mpc_srcbusirq == bus_irq) + break; + if (i >= mp_irq_entries) + return -1; + + *trigger = irq_trigger(i); + *polarity = irq_polarity(i); + return 0; +} + +#endif /* CONFIG_ACPI */ /* * This function currently is only a helper for the i386 smp boot process where @@ -2260,3 +2279,4 @@ void __init setup_ioapic_dest(void) } } #endif + diff -puN arch/x86/pci/acpi.c~git-acpi arch/x86/pci/acpi.c --- a/arch/x86/pci/acpi.c~git-acpi +++ a/arch/x86/pci/acpi.c @@ -13,7 +13,7 @@ static int __devinit can_skip_ioresource return 0; } -static struct dmi_system_id acpi_pciprobe_dmi_table[] = { +static struct dmi_system_id acpi_pciprobe_dmi_table[] __devinitdata = { /* * Systems where PCI IO resource ISA alignment can be skipped * when the ISA enable bit in the bridge control is not set diff -puN drivers/acpi/Kconfig~git-acpi drivers/acpi/Kconfig --- a/drivers/acpi/Kconfig~git-acpi +++ a/drivers/acpi/Kconfig @@ -50,7 +50,6 @@ config ACPI_SLEEP config ACPI_PROCFS bool "Deprecated /proc/acpi files" depends on PROC_FS - default y ---help--- For backwards compatibility, this option allows deprecated /proc/acpi/ files to exist, even when @@ -61,7 +60,6 @@ config ACPI_PROCFS /proc/acpi/info (/sys/modules/acpi/parameters/acpica_version) /proc/acpi/dsdt (/sys/firmware/acpi/tables/DSDT) /proc/acpi/fadt (/sys/firmware/acpi/tables/FACP) - /proc/acpi/battery (/sys/class/power_supply) /proc/acpi/debug_layer (/sys/module/acpi/parameters/debug_layer) /proc/acpi/debug_level (/sys/module/acpi/parameters/debug_level) @@ -69,7 +67,21 @@ config ACPI_PROCFS and functions which do not yet exist in /sys. Say N to delete /proc/acpi/ files that have moved to /sys/ +config ACPI_PROCFS_POWER + bool "Deprecated power /proc/acpi folders" + depends on PROC_FS + default y + ---help--- + For backwards compatibility, this option allows + deprecated power /proc/acpi/ folders to exist, even when + they have been replaced by functions in /sys. + The deprecated folders (and their replacements) include: + /proc/acpi/battery/* (/sys/class/power_supply/*) + /proc/acpi/ac_adapter/* (sys/class/power_supply/*) + This option has no effect on /proc/acpi/ folders + and functions, which do not yet exist in /sys + Say N to delete power /proc/acpi/ folders that have moved to /sys/ config ACPI_PROC_EVENT bool "Deprecated /proc/acpi/event support" depends on PROC_FS diff -puN drivers/acpi/Makefile~git-acpi drivers/acpi/Makefile --- a/drivers/acpi/Makefile~git-acpi +++ a/drivers/acpi/Makefile @@ -58,6 +58,6 @@ obj-$(CONFIG_ACPI_NUMA) += numa.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o -obj-y += cm_sbs.o +obj-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o obj-$(CONFIG_ACPI_SBS) += sbs.o obj-$(CONFIG_ACPI_SBS) += sbshc.o diff -puN drivers/acpi/ac.c~git-acpi drivers/acpi/ac.c --- a/drivers/acpi/ac.c~git-acpi +++ a/drivers/acpi/ac.c @@ -27,7 +27,7 @@ #include #include #include -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER #include #include #endif @@ -51,7 +51,7 @@ MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI AC Adapter Driver"); MODULE_LICENSE("GPL"); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER extern struct proc_dir_entry *acpi_lock_ac_dir(void); extern void *acpi_unlock_ac_dir(struct proc_dir_entry *acpi_ac_dir); static int acpi_ac_open_fs(struct inode *inode, struct file *file); @@ -86,7 +86,7 @@ struct acpi_ac { #define to_acpi_ac(x) container_of(x, struct acpi_ac, charger); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER static const struct file_operations acpi_ac_fops = { .open = acpi_ac_open_fs, .read = seq_read, @@ -136,7 +136,7 @@ static int acpi_ac_get_state(struct acpi return 0; } -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER /* -------------------------------------------------------------------------- FS Interface (/proc) -------------------------------------------------------------------------- */ @@ -275,7 +275,7 @@ static int acpi_ac_add(struct acpi_devic if (result) goto end; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER result = acpi_ac_add_fs(device); #endif if (result) @@ -300,7 +300,7 @@ static int acpi_ac_add(struct acpi_devic end: if (result) { -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_ac_remove_fs(device); #endif kfree(ac); @@ -339,7 +339,7 @@ static int acpi_ac_remove(struct acpi_de ACPI_ALL_NOTIFY, acpi_ac_notify); if (ac->charger.dev) power_supply_unregister(&ac->charger); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_ac_remove_fs(device); #endif @@ -355,7 +355,7 @@ static int __init acpi_ac_init(void) if (acpi_disabled) return -ENODEV; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_ac_dir = acpi_lock_ac_dir(); if (!acpi_ac_dir) return -ENODEV; @@ -363,7 +363,7 @@ static int __init acpi_ac_init(void) result = acpi_bus_register_driver(&acpi_ac_driver); if (result < 0) { -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_unlock_ac_dir(acpi_ac_dir); #endif return -ENODEV; @@ -377,7 +377,7 @@ static void __exit acpi_ac_exit(void) acpi_bus_unregister_driver(&acpi_ac_driver); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_unlock_ac_dir(acpi_ac_dir); #endif diff -puN drivers/acpi/battery.c~git-acpi drivers/acpi/battery.c --- a/drivers/acpi/battery.c~git-acpi +++ a/drivers/acpi/battery.c @@ -31,7 +31,7 @@ #include #include -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER #include #include #include @@ -63,7 +63,7 @@ static unsigned int cache_time = 1000; module_param(cache_time, uint, 0644); MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER extern struct proc_dir_entry *acpi_lock_battery_dir(void); extern void *acpi_unlock_battery_dir(struct proc_dir_entry *acpi_battery_dir); @@ -112,7 +112,7 @@ struct acpi_battery { #define to_acpi_battery(x) container_of(x, struct acpi_battery, bat); -inline int acpi_battery_present(struct acpi_battery *battery) +static int acpi_battery_present(struct acpi_battery *battery) { return battery->device->status.battery_present; } @@ -153,6 +153,8 @@ static int acpi_battery_get_property(str val->intval = POWER_SUPPLY_STATUS_CHARGING; else if (battery->state == 0) val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = acpi_battery_present(battery); @@ -221,8 +223,8 @@ static enum power_supply_property energy POWER_SUPPLY_PROP_MANUFACTURER, }; -#ifdef CONFIG_ACPI_PROCFS -inline char *acpi_battery_units(struct acpi_battery *battery) +#ifdef CONFIG_ACPI_PROCFS_POWER +static char *acpi_battery_units(struct acpi_battery *battery) { return (battery->power_unit)?"mA":"mW"; } @@ -479,7 +481,7 @@ static int acpi_battery_update(struct ac FS Interface (/proc) -------------------------------------------------------------------------- */ -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER static struct proc_dir_entry *acpi_battery_dir; static int acpi_battery_print_info(struct seq_file *seq, int result) @@ -786,7 +788,7 @@ static int acpi_battery_add(struct acpi_ acpi_driver_data(device) = battery; mutex_init(&battery->lock); acpi_battery_update(battery); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER result = acpi_battery_add_fs(device); if (result) goto end; @@ -804,7 +806,7 @@ static int acpi_battery_add(struct acpi_ device->status.battery_present ? "present" : "absent"); end: if (result) { -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_battery_remove_fs(device); #endif kfree(battery); @@ -823,7 +825,7 @@ static int acpi_battery_remove(struct ac status = acpi_remove_notify_handler(device->handle, ACPI_ALL_NOTIFY, acpi_battery_notify); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_battery_remove_fs(device); #endif sysfs_remove_battery(battery); @@ -859,13 +861,13 @@ static int __init acpi_battery_init(void { if (acpi_disabled) return -ENODEV; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_battery_dir = acpi_lock_battery_dir(); if (!acpi_battery_dir) return -ENODEV; #endif if (acpi_bus_register_driver(&acpi_battery_driver) < 0) { -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_unlock_battery_dir(acpi_battery_dir); #endif return -ENODEV; @@ -876,7 +878,7 @@ static int __init acpi_battery_init(void static void __exit acpi_battery_exit(void) { acpi_bus_unregister_driver(&acpi_battery_driver); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_unlock_battery_dir(acpi_battery_dir); #endif } diff -puN drivers/acpi/bay.c~git-acpi drivers/acpi/bay.c --- a/drivers/acpi/bay.c~git-acpi +++ a/drivers/acpi/bay.c @@ -128,7 +128,7 @@ static ssize_t show_present(struct devic return snprintf(buf, PAGE_SIZE, "%d\n", bay_present(bay)); } -DEVICE_ATTR(present, S_IRUGO, show_present, NULL); +static DEVICE_ATTR(present, S_IRUGO, show_present, NULL); /* * write_eject - write method for "eject" file in sysfs @@ -144,7 +144,7 @@ static ssize_t write_eject(struct device eject_device(bay->handle); return count; } -DEVICE_ATTR(eject, S_IWUSR, NULL, write_eject); +static DEVICE_ATTR(eject, S_IWUSR, NULL, write_eject); /** * is_ata - see if a device is an ata device diff -puN drivers/acpi/bus.c~git-acpi drivers/acpi/bus.c --- a/drivers/acpi/bus.c~git-acpi +++ a/drivers/acpi/bus.c @@ -367,7 +367,6 @@ int acpi_bus_receive_event(struct acpi_b return 0; } -EXPORT_SYMBOL(acpi_bus_receive_event); #endif /* CONFIG_ACPI_PROC_EVENT */ /* -------------------------------------------------------------------------- diff -puN drivers/acpi/debug.c~git-acpi drivers/acpi/debug.c --- a/drivers/acpi/debug.c~git-acpi +++ a/drivers/acpi/debug.c @@ -130,6 +130,63 @@ static int param_get_debug_level(char *b 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 -puN drivers/acpi/dock.c~git-acpi drivers/acpi/dock.c --- a/drivers/acpi/dock.c~git-acpi +++ a/drivers/acpi/dock.c @@ -680,7 +680,7 @@ static ssize_t show_docked(struct device return snprintf(buf, PAGE_SIZE, "%d\n", dock_present(dock_station)); } -DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL); +static DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL); /* * show_flags - read method for flags file in sysfs @@ -691,7 +691,7 @@ static ssize_t show_flags(struct device return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags); } -DEVICE_ATTR(flags, S_IRUGO, show_flags, NULL); +static DEVICE_ATTR(flags, S_IRUGO, show_flags, NULL); /* * write_undock - write method for "undock" file in sysfs @@ -707,7 +707,7 @@ static ssize_t write_undock(struct devic ret = handle_eject_request(dock_station, ACPI_NOTIFY_EJECT_REQUEST); return ret ? ret: count; } -DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); +static DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); /* * show_dock_uid - read method for "uid" file in sysfs @@ -723,7 +723,7 @@ static ssize_t show_dock_uid(struct devi return snprintf(buf, PAGE_SIZE, "%lx\n", lbuf); } -DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); +static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); /** * dock_add - add a new dock station diff -puN drivers/acpi/ec.c~git-acpi drivers/acpi/ec.c --- a/drivers/acpi/ec.c~git-acpi +++ a/drivers/acpi/ec.c @@ -75,7 +75,8 @@ enum { EC_FLAGS_WAIT_GPE = 0, /* Don't check status until GPE arrives */ EC_FLAGS_QUERY_PENDING, /* Query is pending */ EC_FLAGS_GPE_MODE, /* Expect GPE to be sent for status change */ - EC_FLAGS_ONLY_IBF_GPE, /* Expect GPE only for IBF = 0 event */ + EC_FLAGS_NO_ADDRESS_GPE, /* Expect GPE only for non-address event */ + EC_FLAGS_ADDRESS, /* Address is being written */ }; static int acpi_ec_remove(struct acpi_device *device, int type); @@ -166,38 +167,45 @@ static inline int acpi_ec_check_status(s static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) { + int ret = 0; + if (unlikely(test_bit(EC_FLAGS_ADDRESS, &ec->flags) && + test_bit(EC_FLAGS_NO_ADDRESS_GPE, &ec->flags))) + force_poll = 1; if (likely(test_bit(EC_FLAGS_GPE_MODE, &ec->flags)) && likely(!force_poll)) { if (wait_event_timeout(ec->wait, acpi_ec_check_status(ec, event), msecs_to_jiffies(ACPI_EC_DELAY))) - return 0; + goto end; clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); if (acpi_ec_check_status(ec, event)) { - if (event == ACPI_EC_EVENT_OBF_1) { - /* miss OBF = 1 GPE, don't expect it anymore */ - printk(KERN_INFO PREFIX "missing OBF_1 confirmation," - "switching to degraded mode.\n"); - set_bit(EC_FLAGS_ONLY_IBF_GPE, &ec->flags); + if (test_bit(EC_FLAGS_ADDRESS, &ec->flags)) { + /* miss address GPE, don't expect it anymore */ + printk(KERN_INFO PREFIX "missing address confirmation, " + "don't expect it any longer.\n"); + set_bit(EC_FLAGS_NO_ADDRESS_GPE, &ec->flags); } else { /* missing GPEs, switch back to poll mode */ - printk(KERN_INFO PREFIX "missing IBF_1 confirmations," + printk(KERN_INFO PREFIX "missing confirmations, " "switch off interrupt mode.\n"); clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); } - return 0; + goto end; } } else { unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); while (time_before(jiffies, delay)) { if (acpi_ec_check_status(ec, event)) - return 0; + goto end; } } printk(KERN_ERR PREFIX "acpi_ec_wait timeout," " status = %d, expect_event = %d\n", acpi_ec_read_status(ec), event); - return -ETIME; + ret = -ETIME; + end: + clear_bit(EC_FLAGS_ADDRESS, &ec->flags); + return ret; } static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, @@ -216,6 +224,9 @@ static int acpi_ec_transaction_unlocked( "write_cmd timeout, command = %d\n", command); goto end; } + /* mark the address byte written to EC */ + if (rdata_len + wdata_len > 1) + set_bit(EC_FLAGS_ADDRESS, &ec->flags); set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); acpi_ec_write_data(ec, *(wdata++)); } @@ -231,8 +242,6 @@ static int acpi_ec_transaction_unlocked( clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); for (; rdata_len > 0; --rdata_len) { - if (test_bit(EC_FLAGS_ONLY_IBF_GPE, &ec->flags)) - force_poll = 1; result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, force_poll); if (result) { printk(KERN_ERR PREFIX "read timeout, command = %d\n", @@ -292,6 +301,8 @@ static int acpi_ec_transaction(struct ac return status; } +#if 0 + /* * Note: samsung nv5000 doesn't work with ec burst mode. * http://bugzilla.kernel.org/show_bug.cgi?id=4980 @@ -307,6 +318,8 @@ int acpi_ec_burst_disable(struct acpi_ec return acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, NULL, 0, NULL, 0, 0); } +#endif /* 0 */ + static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) { int result; @@ -325,6 +338,8 @@ static int acpi_ec_write(struct acpi_ec wdata, 2, NULL, 0, 0); } +#if 0 + /* * Externally callable EC access functions. For now, assume 1 EC only */ @@ -346,6 +361,8 @@ int ec_burst_disable(void) EXPORT_SYMBOL(ec_burst_disable); +#endif /* 0 */ + int ec_read(u8 addr, u8 * val) { int err; @@ -881,12 +898,20 @@ int __init acpi_ec_ecdt_probe(void) boot_ec->gpe = ecdt_ptr->gpe; boot_ec->handle = ACPI_ROOT_OBJECT; } else { + /* This workaround is needed only on some broken machines, + * which require early EC, but fail to provide ECDT */ + acpi_handle x; printk(KERN_DEBUG PREFIX "Look up EC in DSDT\n"); status = acpi_get_devices(ec_device_ids[0].id, ec_parse_device, boot_ec, NULL); /* Check that acpi_get_devices actually find something */ if (ACPI_FAILURE(status) || !boot_ec->handle) goto error; + /* We really need to limit this workaround, the only ASUS, + * which needs it, has fake EC._INI method, so use it as flag. + */ + if (ACPI_FAILURE(acpi_get_handle(boot_ec->handle, "_INI", &x))) + goto error; } ret = ec_install_handlers(boot_ec); diff -puN drivers/acpi/events/evgpe.c~git-acpi drivers/acpi/events/evgpe.c --- a/drivers/acpi/events/evgpe.c~git-acpi +++ a/drivers/acpi/events/evgpe.c @@ -270,18 +270,18 @@ acpi_status acpi_ev_disable_gpe(struct a 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); diff -puN drivers/acpi/glue.c~git-acpi drivers/acpi/glue.c --- a/drivers/acpi/glue.c~git-acpi +++ a/drivers/acpi/glue.c @@ -36,8 +36,6 @@ int register_acpi_bus_type(struct acpi_b return -ENODEV; } -EXPORT_SYMBOL(register_acpi_bus_type); - int unregister_acpi_bus_type(struct acpi_bus_type *type) { if (acpi_disabled) @@ -53,8 +51,6 @@ int unregister_acpi_bus_type(struct acpi return -ENODEV; } -EXPORT_SYMBOL(unregister_acpi_bus_type); - static struct acpi_bus_type *acpi_get_bus_type(struct bus_type *type) { struct acpi_bus_type *tmp, *ret = NULL; diff -puN drivers/acpi/numa.c~git-acpi drivers/acpi/numa.c --- a/drivers/acpi/numa.c~git-acpi +++ a/drivers/acpi/numa.c @@ -78,6 +78,7 @@ int acpi_map_pxm_to_node(int pxm) return node; } +#if 0 void __cpuinit acpi_unmap_pxm_to_node(int node) { int pxm = node_to_pxm_map[node]; @@ -85,6 +86,7 @@ void __cpuinit acpi_unmap_pxm_to_node(in node_to_pxm_map[node] = PXM_INVAL; node_clear(node, nodes_found_map); } +#endif /* 0 */ static void __init acpi_table_print_srat_entry(struct acpi_subtable_header *header) @@ -247,7 +249,6 @@ int acpi_get_pxm(acpi_handle h) } while (ACPI_SUCCESS(status)); return -1; } -EXPORT_SYMBOL(acpi_get_pxm); int acpi_get_node(acpi_handle *handle) { diff -puN drivers/acpi/osl.c~git-acpi drivers/acpi/osl.c --- a/drivers/acpi/osl.c~git-acpi +++ a/drivers/acpi/osl.c @@ -176,8 +176,6 @@ void acpi_os_printf(const char *fmt, ... va_end(args); } -EXPORT_SYMBOL(acpi_os_printf); - void acpi_os_vprintf(const char *fmt, va_list args) { static char buffer[512]; @@ -341,8 +339,6 @@ void acpi_os_sleep(acpi_integer ms) schedule_timeout_interruptible(msecs_to_jiffies(ms)); } -EXPORT_SYMBOL(acpi_os_sleep); - void acpi_os_stall(u32 us) { while (us) { @@ -356,8 +352,6 @@ void acpi_os_stall(u32 us) } } -EXPORT_SYMBOL(acpi_os_stall); - /* * Support ACPI 3.0 AML Timer operand * Returns 64-bit free-running, monotonically increasing timer @@ -387,17 +381,14 @@ acpi_status acpi_os_read_port(acpi_io_ad if (!value) value = &dummy; - switch (width) { - case 8: + *value = 0; + if (width <= 8) { *(u8 *) value = inb(port); - break; - case 16: + } else if (width <= 16) { *(u16 *) value = inw(port); - break; - case 32: + } else if (width <= 32) { *(u32 *) value = inl(port); - break; - default: + } else { BUG(); } @@ -408,17 +399,13 @@ EXPORT_SYMBOL(acpi_os_read_port); acpi_status acpi_os_write_port(acpi_io_address port, u32 value, u32 width) { - switch (width) { - case 8: + if (width <= 8) { outb(value, port); - break; - case 16: + } else if (width <= 16) { outw(value, port); - break; - case 32: + } else if (width <= 32) { outl(value, port); - break; - default: + } else { BUG(); } @@ -514,8 +501,6 @@ acpi_os_read_pci_configuration(struct ac return (result ? AE_ERROR : AE_OK); } -EXPORT_SYMBOL(acpi_os_read_pci_configuration); - acpi_status acpi_os_write_pci_configuration(struct acpi_pci_id * pci_id, u32 reg, acpi_integer value, u32 width) @@ -757,8 +742,6 @@ acpi_os_create_semaphore(u32 max_units, return AE_OK; } -EXPORT_SYMBOL(acpi_os_create_semaphore); - /* * TODO: A better way to delete semaphores? Linux doesn't have a * 'delete_semaphore()' function -- may result in an invalid @@ -782,8 +765,6 @@ acpi_status acpi_os_delete_semaphore(acp return AE_OK; } -EXPORT_SYMBOL(acpi_os_delete_semaphore); - /* * TODO: The kernel doesn't have a 'down_timeout' function -- had to * improvise. The process is to sleep for one scheduler quantum @@ -876,8 +857,6 @@ acpi_status acpi_os_wait_semaphore(acpi_ return status; } -EXPORT_SYMBOL(acpi_os_wait_semaphore); - /* * TODO: Support for units > 1? */ @@ -900,8 +879,6 @@ acpi_status acpi_os_signal_semaphore(acp return AE_OK; } -EXPORT_SYMBOL(acpi_os_signal_semaphore); - #ifdef ACPI_FUTURE_USAGE u32 acpi_os_get_line(char *buffer) { @@ -945,8 +922,6 @@ acpi_status acpi_os_signal(u32 function, return AE_OK; } -EXPORT_SYMBOL(acpi_os_signal); - static int __init acpi_os_name_setup(char *str) { char *p = acpi_os_name; diff -puN drivers/acpi/pci_bind.c~git-acpi drivers/acpi/pci_bind.c --- a/drivers/acpi/pci_bind.c~git-acpi +++ a/drivers/acpi/pci_bind.c @@ -106,6 +106,57 @@ acpi_status acpi_get_pci_id(acpi_handle EXPORT_SYMBOL(acpi_get_pci_id); +static int acpi_pci_unbind(struct acpi_device *device) +{ + int result = 0; + acpi_status status = AE_OK; + struct acpi_pci_data *data = NULL; + char *pathname = NULL; + struct acpi_buffer buffer = { 0, NULL }; + + + if (!device || !device->parent) + return -EINVAL; + + pathname = kzalloc(ACPI_PATHNAME_MAX, GFP_KERNEL); + if (!pathname) + return -ENOMEM; + + buffer.length = ACPI_PATHNAME_MAX; + buffer.pointer = pathname; + acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unbinding PCI device [%s]...\n", + pathname)); + kfree(pathname); + + status = + 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; + } + + status = acpi_detach_data(device->handle, acpi_pci_data_handler); + if (ACPI_FAILURE(status)) { + ACPI_EXCEPTION((AE_INFO, status, + "Unable to detach data from device %s", + acpi_device_bid(device))); + result = -ENODEV; + goto end; + } + if (data->dev->subordinate) { + acpi_pci_irq_del_prt(data->id.segment, data->bus->number); + } + kfree(data); + + end: + return result; +} + int acpi_pci_bind(struct acpi_device *device) { int result = 0; @@ -267,57 +318,6 @@ int acpi_pci_bind(struct acpi_device *de return result; } -int acpi_pci_unbind(struct acpi_device *device) -{ - int result = 0; - acpi_status status = AE_OK; - struct acpi_pci_data *data = NULL; - char *pathname = NULL; - struct acpi_buffer buffer = { 0, NULL }; - - - if (!device || !device->parent) - return -EINVAL; - - pathname = kzalloc(ACPI_PATHNAME_MAX, GFP_KERNEL); - if (!pathname) - return -ENOMEM; - - buffer.length = ACPI_PATHNAME_MAX; - buffer.pointer = pathname; - acpi_get_name(device->handle, ACPI_FULL_PATHNAME, &buffer); - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unbinding PCI device [%s]...\n", - pathname)); - kfree(pathname); - - status = - 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; - } - - status = acpi_detach_data(device->handle, acpi_pci_data_handler); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, - "Unable to detach data from device %s", - acpi_device_bid(device))); - result = -ENODEV; - goto end; - } - if (data->dev->subordinate) { - acpi_pci_irq_del_prt(data->id.segment, data->bus->number); - } - kfree(data); - - end: - return result; -} - int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, struct pci_bus *bus) diff -puN drivers/acpi/pci_irq.c~git-acpi drivers/acpi/pci_irq.c --- a/drivers/acpi/pci_irq.c~git-acpi +++ a/drivers/acpi/pci_irq.c @@ -469,8 +469,6 @@ int acpi_pci_irq_enable(struct pci_dev * return 0; } -EXPORT_SYMBOL(acpi_pci_irq_enable); - /* FIXME: implement x86/x86_64 version */ void __attribute__ ((weak)) acpi_unregister_gsi(u32 i) { diff -puN drivers/acpi/pci_link.c~git-acpi drivers/acpi/pci_link.c --- a/drivers/acpi/pci_link.c~git-acpi +++ a/drivers/acpi/pci_link.c @@ -95,7 +95,7 @@ static struct { int count; struct list_head entries; } acpi_link; -DEFINE_MUTEX(acpi_link_lock); +static DEFINE_MUTEX(acpi_link_lock); /* -------------------------------------------------------------------------- PCI Link Device Management diff -puN drivers/acpi/processor_core.c~git-acpi drivers/acpi/processor_core.c --- a/drivers/acpi/processor_core.c~git-acpi +++ a/drivers/acpi/processor_core.c @@ -612,12 +612,6 @@ static int acpi_processor_get_info(struc request_region(pr->throttling.address, 6, "ACPI CPU throttle"); } -#ifdef CONFIG_CPU_FREQ - acpi_processor_ppc_has_changed(pr); -#endif - acpi_processor_get_throttling_info(pr); - acpi_processor_get_limit_info(pr); - return 0; } @@ -647,7 +641,7 @@ static int __cpuinit acpi_processor_star */ if (processor_device_array[pr->id] != NULL && processor_device_array[pr->id] != device) { - printk(KERN_WARNING "BIOS reported wrong ACPI id" + printk(KERN_WARNING "BIOS reported wrong ACPI id " "for the processor\n"); return -ENODEV; } @@ -665,6 +659,12 @@ static int __cpuinit acpi_processor_star /* _PDC call should be done before doing anything else (if reqd.). */ arch_acpi_processor_init_pdc(pr); acpi_processor_set_pdc(pr); +#ifdef CONFIG_CPU_FREQ + acpi_processor_ppc_has_changed(pr); +#endif + acpi_processor_get_throttling_info(pr); + acpi_processor_get_limit_info(pr); + acpi_processor_power_init(pr, device); diff -puN drivers/acpi/processor_idle.c~git-acpi drivers/acpi/processor_idle.c --- a/drivers/acpi/processor_idle.c~git-acpi +++ a/drivers/acpi/processor_idle.c @@ -197,6 +197,19 @@ static inline u32 ticks_elapsed_in_us(u3 return PM_TIMER_TICKS_TO_US((0xFFFFFFFF - t1) + t2); } +static void acpi_safe_halt(void) +{ + current_thread_info()->status &= ~TS_POLLING; + /* + * TS_POLLING-cleared state must be visible before we + * test NEED_RESCHED: + */ + smp_mb(); + if (!need_resched()) + safe_halt(); + current_thread_info()->status |= TS_POLLING; +} + #ifndef CONFIG_CPU_IDLE static void @@ -239,19 +252,6 @@ acpi_processor_power_activate(struct acp return; } -static void acpi_safe_halt(void) -{ - current_thread_info()->status &= ~TS_POLLING; - /* - * TS_POLLING-cleared state must be visible before we - * test NEED_RESCHED: - */ - smp_mb(); - if (!need_resched()) - safe_halt(); - current_thread_info()->status |= TS_POLLING; -} - static atomic_t c3_cpu_count; /* Common C-state entry for C2, C3, .. */ @@ -1373,15 +1373,7 @@ static int acpi_idle_enter_c1(struct cpu if (pr->flags.bm_check) acpi_idle_update_bm_rld(pr, cx); - current_thread_info()->status &= ~TS_POLLING; - /* - * TS_POLLING-cleared state must be visible before we test - * NEED_RESCHED: - */ - smp_mb(); - if (!need_resched()) - safe_halt(); - current_thread_info()->status |= TS_POLLING; + acpi_safe_halt(); cx->usage++; @@ -1399,6 +1391,8 @@ static int acpi_idle_enter_simple(struct struct acpi_processor *pr; struct acpi_processor_cx *cx = cpuidle_get_statedata(state); u32 t1, t2; + int sleep_ticks = 0; + pr = processors[smp_processor_id()]; if (unlikely(!pr)) @@ -1428,6 +1422,8 @@ static int acpi_idle_enter_simple(struct ACPI_FLUSH_CPU_CACHE(); t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); + /* Tell the scheduler that we are going deep-idle: */ + sched_clock_idle_sleep_event(); acpi_state_timer_broadcast(pr, cx, 1); acpi_idle_do_entry(cx); t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); @@ -1436,6 +1432,10 @@ static int acpi_idle_enter_simple(struct /* TSC could halt in idle, so notify users */ mark_tsc_unstable("TSC halts in idle");; #endif + sleep_ticks = ticks_elapsed(t1, t2); + + /* Tell the scheduler how much we idled: */ + sched_clock_idle_wakeup_event(sleep_ticks*PM_TIMER_TICK_NS); local_irq_enable(); current_thread_info()->status |= TS_POLLING; @@ -1443,7 +1443,7 @@ static int acpi_idle_enter_simple(struct cx->usage++; acpi_state_timer_broadcast(pr, cx, 0); - cx->time += ticks_elapsed(t1, t2); + cx->time += sleep_ticks; return ticks_elapsed_in_us(t1, t2); } @@ -1463,6 +1463,8 @@ static int acpi_idle_enter_bm(struct cpu struct acpi_processor *pr; struct acpi_processor_cx *cx = cpuidle_get_statedata(state); u32 t1, t2; + int sleep_ticks = 0; + pr = processors[smp_processor_id()]; if (unlikely(!pr)) @@ -1471,6 +1473,15 @@ static int acpi_idle_enter_bm(struct cpu if (acpi_idle_suspend) return(acpi_idle_enter_c1(dev, state)); + if (acpi_idle_bm_check()) { + if (dev->safe_state) { + return dev->safe_state->enter(dev, dev->safe_state); + } else { + acpi_safe_halt(); + return 0; + } + } + local_irq_disable(); current_thread_info()->status &= ~TS_POLLING; /* @@ -1485,38 +1496,45 @@ static int acpi_idle_enter_bm(struct cpu return 0; } + /* Tell the scheduler that we are going deep-idle: */ + sched_clock_idle_sleep_event(); /* * Must be done before busmaster disable as we might need to * access HPET ! */ acpi_state_timer_broadcast(pr, cx, 1); - if (acpi_idle_bm_check()) { - cx = pr->power.bm_state; - - acpi_idle_update_bm_rld(pr, cx); - - t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); - acpi_idle_do_entry(cx); - t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); - } else { - acpi_idle_update_bm_rld(pr, cx); + acpi_idle_update_bm_rld(pr, cx); + /* + * disable bus master + * bm_check implies we need ARB_DIS + * !bm_check implies we need cache flush + * bm_control implies whether we can do ARB_DIS + * + * That leaves a case where bm_check is set and bm_control is + * not set. In that case we cannot do much, we enter C3 + * without doing anything. + */ + if (pr->flags.bm_check && pr->flags.bm_control) { spin_lock(&c3_lock); c3_cpu_count++; /* Disable bus master arbitration when all CPUs are in C3 */ if (c3_cpu_count == num_online_cpus()) acpi_set_register(ACPI_BITREG_ARB_DISABLE, 1); spin_unlock(&c3_lock); + } else if (!pr->flags.bm_check) { + ACPI_FLUSH_CPU_CACHE(); + } - t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); - acpi_idle_do_entry(cx); - t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); + t1 = inl(acpi_gbl_FADT.xpm_timer_block.address); + acpi_idle_do_entry(cx); + t2 = inl(acpi_gbl_FADT.xpm_timer_block.address); + /* Re-enable bus master arbitration */ + if (pr->flags.bm_check && pr->flags.bm_control) { spin_lock(&c3_lock); - /* Re-enable bus master arbitration */ - if (c3_cpu_count == num_online_cpus()) - acpi_set_register(ACPI_BITREG_ARB_DISABLE, 0); + acpi_set_register(ACPI_BITREG_ARB_DISABLE, 0); c3_cpu_count--; spin_unlock(&c3_lock); } @@ -1525,6 +1543,9 @@ static int acpi_idle_enter_bm(struct cpu /* TSC could halt in idle, so notify users */ mark_tsc_unstable("TSC halts in idle"); #endif + sleep_ticks = ticks_elapsed(t1, t2); + /* Tell the scheduler how much we idled: */ + sched_clock_idle_wakeup_event(sleep_ticks*PM_TIMER_TICK_NS); local_irq_enable(); current_thread_info()->status |= TS_POLLING; @@ -1532,7 +1553,7 @@ static int acpi_idle_enter_bm(struct cpu cx->usage++; acpi_state_timer_broadcast(pr, cx, 0); - cx->time += ticks_elapsed(t1, t2); + cx->time += sleep_ticks; return ticks_elapsed_in_us(t1, t2); } @@ -1584,12 +1605,14 @@ static int acpi_processor_setup_cpuidle( case ACPI_STATE_C1: state->flags |= CPUIDLE_FLAG_SHALLOW; state->enter = acpi_idle_enter_c1; + dev->safe_state = state; break; case ACPI_STATE_C2: state->flags |= CPUIDLE_FLAG_BALANCED; state->flags |= CPUIDLE_FLAG_TIME_VALID; state->enter = acpi_idle_enter_simple; + dev->safe_state = state; break; case ACPI_STATE_C3: @@ -1610,14 +1633,6 @@ static int acpi_processor_setup_cpuidle( if (!count) return -EINVAL; - /* find the deepest state that can handle active BM */ - if (pr->flags.bm_check) { - for (i = 1; i < ACPI_PROCESSOR_MAX_POWER && i <= max_cstate; i++) - if (pr->power.states[i].type == ACPI_STATE_C3) - break; - pr->power.bm_state = &pr->power.states[i-1]; - } - return 0; } diff -puN drivers/acpi/processor_throttling.c~git-acpi drivers/acpi/processor_throttling.c --- a/drivers/acpi/processor_throttling.c~git-acpi +++ a/drivers/acpi/processor_throttling.c @@ -70,7 +70,55 @@ static int acpi_processor_get_platform_l int acpi_processor_tstate_has_changed(struct acpi_processor *pr) { - return acpi_processor_get_platform_limit(pr); + int result = 0; + int throttling_limit; + int current_state; + struct acpi_processor_limit *limit; + int target_state; + + result = acpi_processor_get_platform_limit(pr); + if (result) { + /* Throttling Limit is unsupported */ + return result; + } + + throttling_limit = pr->throttling_platform_limit; + if (throttling_limit >= pr->throttling.state_count) { + /* Uncorrect Throttling Limit */ + return -EINVAL; + } + + current_state = pr->throttling.state; + if (current_state > throttling_limit) { + /* + * The current state can meet the requirement of + * _TPC limit. But it is reasonable that OSPM changes + * t-states from high to low for better performance. + * Of course the limit condition of thermal + * and user should be considered. + */ + limit = &pr->limit; + target_state = throttling_limit; + if (limit->thermal.tx > target_state) + target_state = limit->thermal.tx; + if (limit->user.tx > target_state) + target_state = limit->user.tx; + } else if (current_state == throttling_limit) { + /* + * Unnecessary to change the throttling state + */ + return 0; + } else { + /* + * If the current state is lower than the limit of _TPC, it + * will be forced to switch to the throttling state defined + * by throttling_platfor_limit. + * Because the previous state meets with the limit condition + * of thermal and user, it is unnecessary to check it again. + */ + target_state = throttling_limit; + } + return acpi_processor_set_throttling(pr, target_state); } /* @@ -83,6 +131,7 @@ static int acpi_processor_get_throttling struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *ptc = NULL; union acpi_object obj = { 0 }; + struct acpi_processor_throttling *throttling; status = acpi_evaluate_object(pr->handle, "_PTC", NULL, &buffer); if (ACPI_FAILURE(status)) { @@ -134,6 +183,22 @@ static int acpi_processor_get_throttling memcpy(&pr->throttling.status_register, obj.buffer.pointer, sizeof(struct acpi_ptc_register)); + throttling = &pr->throttling; + + if ((throttling->control_register.bit_width + + throttling->control_register.bit_offset) > 32) { + printk(KERN_ERR PREFIX "Invalid _PTC control register\n"); + result = -EFAULT; + goto end; + } + + if ((throttling->status_register.bit_width + + throttling->status_register.bit_offset) > 32) { + printk(KERN_ERR PREFIX "Invalid _PTC status register\n"); + result = -EFAULT; + goto end; + } + end: kfree(buffer.pointer); @@ -328,44 +393,132 @@ static int acpi_processor_get_throttling return 0; } -static int acpi_read_throttling_status(struct acpi_processor_throttling - *throttling) +#ifdef CONFIG_X86 +static int acpi_throttling_rdmsr(struct acpi_processor *pr, + acpi_integer * value) +{ + struct cpuinfo_x86 *c; + u64 msr_high, msr_low; + unsigned int cpu; + u64 msr = 0; + int ret = -1; + + cpu = pr->id; + c = &cpu_data(cpu); + + if ((c->x86_vendor != X86_VENDOR_INTEL) || + !cpu_has(c, X86_FEATURE_ACPI)) { + printk(KERN_ERR PREFIX + "HARDWARE addr space,NOT supported yet\n"); + } else { + msr_low = 0; + msr_high = 0; + rdmsr_on_cpu(cpu, MSR_IA32_THERM_CONTROL, + (u32 *)&msr_low , (u32 *) &msr_high); + msr = (msr_high << 32) | msr_low; + *value = (acpi_integer) msr; + ret = 0; + } + return ret; +} + +static int acpi_throttling_wrmsr(struct acpi_processor *pr, acpi_integer value) { - int value = -1; + struct cpuinfo_x86 *c; + unsigned int cpu; + int ret = -1; + u64 msr; + + cpu = pr->id; + c = &cpu_data(cpu); + + if ((c->x86_vendor != X86_VENDOR_INTEL) || + !cpu_has(c, X86_FEATURE_ACPI)) { + printk(KERN_ERR PREFIX + "HARDWARE addr space,NOT supported yet\n"); + } else { + msr = value; + wrmsr_on_cpu(cpu, MSR_IA32_THERM_CONTROL, + msr & 0xffffffff, msr >> 32); + ret = 0; + } + return ret; +} +#else +static int acpi_throttling_rdmsr(struct acpi_processor *pr, + acpi_integer * value) +{ + printk(KERN_ERR PREFIX + "HARDWARE addr space,NOT supported yet\n"); + return -1; +} + +static int acpi_throttling_wrmsr(struct acpi_processor *pr, acpi_integer value) +{ + printk(KERN_ERR PREFIX + "HARDWARE addr space,NOT supported yet\n"); + return -1; +} +#endif + +static int acpi_read_throttling_status(struct acpi_processor *pr, + acpi_integer *value) +{ + u32 bit_width, bit_offset; + u64 ptc_value; + u64 ptc_mask; + struct acpi_processor_throttling *throttling; + int ret = -1; + + throttling = &pr->throttling; switch (throttling->status_register.space_id) { case ACPI_ADR_SPACE_SYSTEM_IO: + ptc_value = 0; + bit_width = throttling->status_register.bit_width; + bit_offset = throttling->status_register.bit_offset; + acpi_os_read_port((acpi_io_address) throttling->status_register. - address, &value, - (u32) throttling->status_register.bit_width * - 8); + address, (u32 *) &ptc_value, + (u32) (bit_width + bit_offset)); + ptc_mask = (1 << bit_width) - 1; + *value = (acpi_integer) ((ptc_value >> bit_offset) & ptc_mask); + ret = 0; break; case ACPI_ADR_SPACE_FIXED_HARDWARE: - printk(KERN_ERR PREFIX - "HARDWARE addr space,NOT supported yet\n"); + ret = acpi_throttling_rdmsr(pr, value); break; default: printk(KERN_ERR PREFIX "Unknown addr space %d\n", (u32) (throttling->status_register.space_id)); } - return value; + return ret; } -static int acpi_write_throttling_state(struct acpi_processor_throttling - *throttling, int value) +static int acpi_write_throttling_state(struct acpi_processor *pr, + acpi_integer value) { + u32 bit_width, bit_offset; + u64 ptc_value; + u64 ptc_mask; + struct acpi_processor_throttling *throttling; int ret = -1; + throttling = &pr->throttling; switch (throttling->control_register.space_id) { case ACPI_ADR_SPACE_SYSTEM_IO: + bit_width = throttling->control_register.bit_width; + bit_offset = throttling->control_register.bit_offset; + ptc_mask = (1 << bit_width) - 1; + ptc_value = value & ptc_mask; + acpi_os_write_port((acpi_io_address) throttling-> - control_register.address, value, - (u32) throttling->control_register. - bit_width * 8); + control_register.address, + (u32) (ptc_value << bit_offset), + (u32) (bit_width + bit_offset)); ret = 0; break; case ACPI_ADR_SPACE_FIXED_HARDWARE: - printk(KERN_ERR PREFIX - "HARDWARE addr space,NOT supported yet\n"); + ret = acpi_throttling_wrmsr(pr, value); break; default: printk(KERN_ERR PREFIX "Unknown addr space %d\n", @@ -374,7 +527,8 @@ static int acpi_write_throttling_state(s return ret; } -static int acpi_get_throttling_state(struct acpi_processor *pr, int value) +static int acpi_get_throttling_state(struct acpi_processor *pr, + acpi_integer value) { int i; @@ -390,22 +544,26 @@ static int acpi_get_throttling_state(str return i; } -static int acpi_get_throttling_value(struct acpi_processor *pr, int state) +static int acpi_get_throttling_value(struct acpi_processor *pr, + int state, acpi_integer *value) { - int value = -1; + int ret = -1; + if (state >= 0 && state <= pr->throttling.state_count) { struct acpi_processor_tx_tss *tx = (struct acpi_processor_tx_tss *)&(pr->throttling. states_tss[state]); - value = tx->control; + *value = tx->control; + ret = 0; } - return value; + return ret; } static int acpi_processor_get_throttling_ptc(struct acpi_processor *pr) { int state = 0; - u32 value = 0; + int ret; + acpi_integer value; if (!pr) return -EINVAL; @@ -415,8 +573,9 @@ static int acpi_processor_get_throttling pr->throttling.state = 0; local_irq_disable(); - value = acpi_read_throttling_status(&pr->throttling); - if (value >= 0) { + value = 0; + ret = acpi_read_throttling_status(pr, &value); + if (ret >= 0) { state = acpi_get_throttling_state(pr, value); pr->throttling.state = state; } @@ -430,6 +589,40 @@ static int acpi_processor_get_throttling return pr->throttling.acpi_processor_get_throttling(pr); } +static int acpi_processor_get_fadt_info(struct acpi_processor *pr) +{ + int i, step; + + if (!pr->throttling.address) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No throttling register\n")); + return -EINVAL; + } else if (!pr->throttling.duty_width) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No throttling states\n")); + return -EINVAL; + } + /* TBD: Support duty_cycle values that span bit 4. */ + else if ((pr->throttling.duty_offset + pr->throttling.duty_width) > 4) { + printk(KERN_WARNING PREFIX "duty_cycle spans bit 4\n"); + return -EINVAL; + } + + pr->throttling.state_count = 1 << acpi_gbl_FADT.duty_width; + + /* + * Compute state values. Note that throttling displays a linear power + * performance relationship (at 50% performance the CPU will consume + * 50% power). Values are in 1/10th of a percent to preserve accuracy. + */ + + step = (1000 / pr->throttling.state_count); + + for (i = 0; i < pr->throttling.state_count; i++) { + pr->throttling.states[i].performance = 1000 - step * i; + pr->throttling.states[i].power = 1000 - step * i; + } + return 0; +} + static int acpi_processor_set_throttling_fadt(struct acpi_processor *pr, int state) { @@ -506,7 +699,8 @@ static int acpi_processor_set_throttling static int acpi_processor_set_throttling_ptc(struct acpi_processor *pr, int state) { - u32 value = 0; + int ret; + acpi_integer value; if (!pr) return -EINVAL; @@ -524,10 +718,10 @@ static int acpi_processor_set_throttling return -EPERM; local_irq_disable(); - - value = acpi_get_throttling_value(pr, state); - if (value >= 0) { - acpi_write_throttling_state(&pr->throttling, value); + value = 0; + ret = acpi_get_throttling_value(pr, state, &value); + if (ret >= 0) { + acpi_write_throttling_state(pr, value); pr->throttling.state = state; } local_irq_enable(); @@ -543,8 +737,6 @@ int acpi_processor_set_throttling(struct int acpi_processor_get_throttling_info(struct acpi_processor *pr) { int result = 0; - int step = 0; - int i = 0; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "pblk_address[0x%08x] duty_offset[%d] duty_width[%d]\n", @@ -563,6 +755,8 @@ int acpi_processor_get_throttling_info(s acpi_processor_get_throttling_states(pr) || acpi_processor_get_platform_limit(pr)) { + if (acpi_processor_get_fadt_info(pr)) + return 0; pr->throttling.acpi_processor_get_throttling = &acpi_processor_get_throttling_fadt; pr->throttling.acpi_processor_set_throttling = @@ -576,19 +770,6 @@ int acpi_processor_get_throttling_info(s acpi_processor_get_tsd(pr); - if (!pr->throttling.address) { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No throttling register\n")); - return 0; - } else if (!pr->throttling.duty_width) { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No throttling states\n")); - return 0; - } - /* TBD: Support duty_cycle values that span bit 4. */ - else if ((pr->throttling.duty_offset + pr->throttling.duty_width) > 4) { - printk(KERN_WARNING PREFIX "duty_cycle spans bit 4\n"); - return 0; - } - /* * PIIX4 Errata: We don't support throttling on the original PIIX4. * This shouldn't be an issue as few (if any) mobile systems ever @@ -600,21 +781,6 @@ int acpi_processor_get_throttling_info(s return 0; } - pr->throttling.state_count = 1 << acpi_gbl_FADT.duty_width; - - /* - * Compute state values. Note that throttling displays a linear power/ - * performance relationship (at 50% performance the CPU will consume - * 50% power). Values are in 1/10th of a percent to preserve accuracy. - */ - - step = (1000 / pr->throttling.state_count); - - for (i = 0; i < pr->throttling.state_count; i++) { - pr->throttling.states[i].performance = step * i; - pr->throttling.states[i].power = step * i; - } - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d throttling states\n", pr->throttling.state_count)); diff -puN drivers/acpi/sbs.c~git-acpi drivers/acpi/sbs.c --- a/drivers/acpi/sbs.c~git-acpi +++ a/drivers/acpi/sbs.c @@ -29,7 +29,7 @@ #include #include -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER #include #include #include @@ -88,7 +88,7 @@ MODULE_DEVICE_TABLE(acpi, sbs_device_ids struct acpi_battery { struct power_supply bat; struct acpi_sbs *sbs; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER struct proc_dir_entry *proc_entry; #endif unsigned long update_time; @@ -113,6 +113,7 @@ struct acpi_battery { u16 spec; u8 id; u8 present:1; + u8 have_sysfs_alarm:1; }; #define to_acpi_battery(x) container_of(x, struct acpi_battery, bat); @@ -122,7 +123,7 @@ struct acpi_sbs { struct acpi_device *device; struct acpi_smb_hc *hc; struct mutex lock; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER struct proc_dir_entry *charger_entry; #endif struct acpi_battery battery[MAX_SBS_BAT]; @@ -468,7 +469,7 @@ static struct device_attribute alarm_att FS Interface (/proc/acpi) -------------------------------------------------------------------------- */ -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER /* Generic Routines */ static int acpi_sbs_add_fs(struct proc_dir_entry **dir, @@ -789,7 +790,7 @@ static int acpi_battery_add(struct acpi_ return result; sprintf(battery->name, ACPI_BATTERY_DIR_NAME, id); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_sbs_add_fs(&battery->proc_entry, acpi_battery_dir, battery->name, &acpi_battery_info_fops, &acpi_battery_state_fops, &acpi_battery_alarm_fops, @@ -808,7 +809,13 @@ static int acpi_battery_add(struct acpi_ } battery->bat.get_property = acpi_sbs_battery_get_property; result = power_supply_register(&sbs->device->dev, &battery->bat); - device_create_file(battery->bat.dev, &alarm_attr); + if (result) + goto end; + result = device_create_file(battery->bat.dev, &alarm_attr); + if (result) + goto end; + battery->have_sysfs_alarm = 1; + end: printk(KERN_INFO PREFIX "%s [%s]: Battery Slot [%s] (battery %s)\n", ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), battery->name, sbs->battery->present ? "present" : "absent"); @@ -817,14 +824,16 @@ static int acpi_battery_add(struct acpi_ static void acpi_battery_remove(struct acpi_sbs *sbs, int id) { - if (sbs->battery[id].bat.dev) - device_remove_file(sbs->battery[id].bat.dev, &alarm_attr); - power_supply_unregister(&sbs->battery[id].bat); -#ifdef CONFIG_ACPI_PROCFS - if (sbs->battery[id].proc_entry) { - acpi_sbs_remove_fs(&(sbs->battery[id].proc_entry), - acpi_battery_dir); - } + struct acpi_battery *battery = &sbs->battery[id]; + + if (battery->bat.dev) { + if (battery->have_sysfs_alarm) + device_remove_file(battery->bat.dev, &alarm_attr); + power_supply_unregister(&battery->bat); + } +#ifdef CONFIG_ACPI_PROCFS_POWER + if (battery->proc_entry) + acpi_sbs_remove_fs(&battery->proc_entry, acpi_battery_dir); #endif } @@ -835,7 +844,7 @@ static int acpi_charger_add(struct acpi_ result = acpi_ac_get_present(sbs); if (result) goto end; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER result = acpi_sbs_add_fs(&sbs->charger_entry, acpi_ac_dir, ACPI_AC_DIR_NAME, NULL, &acpi_ac_state_fops, NULL, sbs); @@ -859,13 +868,13 @@ static void acpi_charger_remove(struct a { if (sbs->charger.dev) power_supply_unregister(&sbs->charger); -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER if (sbs->charger_entry) acpi_sbs_remove_fs(&sbs->charger_entry, acpi_ac_dir); #endif } -void acpi_sbs_callback(void *context) +static void acpi_sbs_callback(void *context) { int id; struct acpi_sbs *sbs = context; @@ -965,7 +974,7 @@ static int acpi_sbs_remove(struct acpi_d static void acpi_sbs_rmdirs(void) { -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER if (acpi_ac_dir) { acpi_unlock_ac_dir(acpi_ac_dir); acpi_ac_dir = NULL; @@ -1004,7 +1013,7 @@ static int __init acpi_sbs_init(void) if (acpi_disabled) return -ENODEV; -#ifdef CONFIG_ACPI_PROCFS +#ifdef CONFIG_ACPI_PROCFS_POWER acpi_ac_dir = acpi_lock_ac_dir(); if (!acpi_ac_dir) return -ENODEV; diff -puN drivers/acpi/sbshc.c~git-acpi drivers/acpi/sbshc.c --- a/drivers/acpi/sbshc.c~git-acpi +++ a/drivers/acpi/sbshc.c @@ -111,8 +111,8 @@ static int wait_transaction_complete(str return -ETIME; } -int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol, u8 address, - u8 command, u8 *data, u8 length) +static int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol, + u8 address, u8 command, u8 *data, u8 length) { int ret = -EFAULT, i; u8 temp, sz = 0; diff -puN drivers/acpi/sleep/main.c~git-acpi drivers/acpi/sleep/main.c --- a/drivers/acpi/sleep/main.c~git-acpi +++ a/drivers/acpi/sleep/main.c @@ -28,7 +28,7 @@ u8 sleep_states[ACPI_S_STATE_COUNT]; static u32 acpi_target_sleep_state = ACPI_STATE_S0; #endif -int acpi_sleep_prepare(u32 acpi_state) +static int acpi_sleep_prepare(u32 acpi_state) { #ifdef CONFIG_ACPI_SLEEP /* do we have a wakeup address for S2 and S3? */ @@ -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 -puN drivers/acpi/sleep/sleep.h~git-acpi drivers/acpi/sleep/sleep.h --- a/drivers/acpi/sleep/sleep.h~git-acpi +++ a/drivers/acpi/sleep/sleep.h @@ -6,4 +6,3 @@ extern void acpi_enable_wakeup_device_pr extern void acpi_enable_wakeup_device(u8 sleep_state); extern void acpi_disable_wakeup_device(u8 sleep_state); -extern int acpi_sleep_prepare(u32 acpi_state); diff -puN drivers/acpi/tables/tbutils.c~git-acpi drivers/acpi/tables/tbutils.c --- a/drivers/acpi/tables/tbutils.c~git-acpi +++ a/drivers/acpi/tables/tbutils.c @@ -449,7 +449,7 @@ acpi_tb_parse_root_table(acpi_physical_a /* XSDT has NULL entry, RSDT is used */ address = rsdt_address; table_entry_size = sizeof(u32); - ACPI_WARNING((AE_INFO, "BIOS XSDT has NULL entry," + ACPI_WARNING((AE_INFO, "BIOS XSDT has NULL entry, " "using RSDT")); } } diff -puN drivers/acpi/video.c~git-acpi drivers/acpi/video.c --- a/drivers/acpi/video.c~git-acpi +++ a/drivers/acpi/video.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -55,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 @@ -75,8 +74,8 @@ static int acpi_video_bus_add(struct acp 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); @@ -87,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 { @@ -98,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 { @@ -135,8 +136,8 @@ struct acpi_video_bus { u8 attached_count; struct acpi_video_bus_cap cap; struct acpi_video_bus_flags flags; - struct semaphore sem; struct list_head video_device_list; + struct mutex device_list_lock; /* protects video_device_list */ struct proc_dir_entry *dir; struct input_dev *input; char phys[32]; /* for input device */ @@ -285,24 +286,27 @@ static void acpi_video_switch_brightness 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; } @@ -315,17 +319,18 @@ static struct backlight_ops acpi_backlig 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); } @@ -342,37 +347,25 @@ static struct output_properties acpi_out 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 @@ -383,26 +376,25 @@ acpi_video_device_lcd_query_levels(struc 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; } @@ -413,7 +405,6 @@ acpi_video_device_lcd_set_level(struct a union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - arg0.integer.value = level; if (device->cap._BCM) @@ -444,11 +435,11 @@ acpi_video_device_EDID(struct acpi_video 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) @@ -456,7 +447,8 @@ acpi_video_device_EDID(struct acpi_video 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; @@ -483,12 +475,12 @@ acpi_video_bus_set_POST(struct acpi_vide 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; } @@ -496,11 +488,7 @@ acpi_video_bus_set_POST(struct acpi_vide 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 @@ -509,7 +497,8 @@ acpi_video_bus_POST_options(struct acpi_ { 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; @@ -517,23 +506,25 @@ acpi_video_bus_POST_options(struct acpi_ /* * 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 @@ -543,178 +534,199 @@ acpi_video_bus_DOS(struct acpi_video_bus 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; - memset(&device->cap, 0, 4); + *max_level = 0; - 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_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_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))) { - - 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); +} + +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]; - 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); - } - 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); + 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); } - return; + + 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); + } + + device->output_dev = od; + return 0; } /* - * Arg: - * device : video output device (VGA) + * Arg: + * device : video output device (LCD, CRT, ..) * * Return Value: - * None + * 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) + * + * Return Value: + * 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); } /* @@ -726,10 +738,6 @@ static int acpi_video_bus_check(struct a { 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. */ @@ -767,10 +775,6 @@ static int acpi_video_device_info_seq_sh { 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) @@ -786,7 +790,6 @@ static int acpi_video_device_info_seq_sh seq_printf(seq, "known by bios: %s\n", dev->flags.bios ? "yes" : "no"); - end: return 0; } @@ -803,10 +806,6 @@ static int acpi_video_device_state_seq_s 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)) @@ -821,7 +820,6 @@ static int acpi_video_device_state_seq_s else seq_printf(seq, "\n"); - end: return 0; } @@ -843,7 +841,6 @@ acpi_video_device_write_state(struct fil char str[12] = { 0 }; u32 state = 0; - if (!dev || count + 1 > sizeof str) return -EINVAL; @@ -868,8 +865,7 @@ acpi_video_device_brightness_seq_show(st struct acpi_video_device *dev = seq->private; int i; - - if (!dev || !dev->brightness) { + if (!dev->brightness) { seq_printf(seq, "\n"); return 0; } @@ -896,11 +892,10 @@ acpi_video_device_write_brightness(struc { struct seq_file *m = file->private_data; struct acpi_video_device *dev = m->private; - char str[4] = { 0 }; + char str[5] = { 0 }; unsigned int level = 0; int i; - if (!dev || !dev->brightness || count + 1 > sizeof str) return -EINVAL; @@ -932,25 +927,18 @@ static int acpi_video_device_EDID_seq_sh 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 @@ -968,87 +956,90 @@ acpi_video_device_EDID_open_fs(struct in 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; } @@ -1093,7 +1084,7 @@ static int acpi_video_bus_ROM_seq_show(s printk(KERN_INFO PREFIX "Please implement %s\n", __FUNCTION__); seq_printf(seq, "\n"); - end: + end: return 0; } @@ -1108,17 +1099,15 @@ static int acpi_video_bus_POST_info_seq_ 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: "); @@ -1129,7 +1118,7 @@ static int acpi_video_bus_POST_info_seq_ seq_putc(seq, '\n'); } else seq_printf(seq, "\n"); - end: + return 0; } @@ -1146,10 +1135,6 @@ static int acpi_video_bus_POST_seq_show( 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"); @@ -1157,7 +1142,7 @@ static int acpi_video_bus_POST_seq_show( } seq_printf(seq, "device POSTed is <%s>\n", device_decode[id & 3]); - end: + end: return 0; } @@ -1165,7 +1150,6 @@ static int acpi_video_bus_DOS_seq_show(s { struct acpi_video_bus *video = seq->private; - seq_printf(seq, "DOS setting: <%d>\n", video->dos_setting); return 0; @@ -1193,7 +1177,6 @@ acpi_video_bus_write_POST(struct file *f char str[12] = { 0 }; unsigned long opt, options; - if (!video || count + 1 > sizeof str) return -EINVAL; @@ -1255,94 +1238,91 @@ acpi_video_bus_write_DOS(struct file *fi 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; + device_dir = proc_mkdir(acpi_device_bid(device), acpi_video_dir); + if (!device_dir) + return -ENOMEM; - video = acpi_driver_data(device); - - 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); + struct proc_dir_entry *device_dir = acpi_device_dir(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)); + 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; } @@ -1355,130 +1335,173 @@ static int acpi_video_bus_remove_fs(stru -------------------------------------------------------------------------- */ /* 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; - - data->device_id = device_id; - data->video = video; - data->dev = device; - - attribute = acpi_video_get_device_attr(video, device_id); - - 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; - } + strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); + acpi_driver_data(device) = data; - down(&video->sem); - list_add_tail(&data->entry, &video->video_device_list); - up(&video->sem); + data->device_id = device_id; + data->video = video; + data->dev = device; - acpi_video_device_add_fs(device); + acpi_video_get_device_flags(data); + acpi_video_device_find_cap(data); + acpi_video_get_brightness_levels(data, &max_brightness); - return 0; + acpi_video_device_bind(video, data); + + 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; } - return -ENOENT; + if (data->cap._DCS && data->cap._DSS) { + error = acpi_video_register_output(data); + if (error) + goto err_remove_backlight; + } + + 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; + } + + 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 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. */ static void acpi_video_device_rebind(struct acpi_video_bus *video) { - struct list_head *node, *next; - list_for_each_safe(node, next, &video->video_device_list) { - struct acpi_video_device *dev = - container_of(node, struct acpi_video_device, entry); + struct acpi_video_device *dev; + + mutex_lock(&video->device_list_lock); + + list_for_each_entry(dev, &video->video_device_list, entry) acpi_video_device_bind(video, dev); - } + + mutex_unlock(&video->device_list_lock); } /* * 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. */ @@ -1487,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 * */ @@ -1519,19 +1538,20 @@ static int acpi_video_device_enumerate(s 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; @@ -1540,13 +1560,10 @@ static int acpi_video_device_enumerate(s 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; } @@ -1556,35 +1573,36 @@ static int acpi_video_device_enumerate(s 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. @@ -1592,30 +1610,33 @@ static int acpi_video_device_enumerate(s static int acpi_video_switch_output(struct acpi_video_bus *video, int event) { - struct list_head *node, *next; + struct list_head *node; struct acpi_video_device *dev = NULL; struct acpi_video_device *dev_next = NULL; struct acpi_video_device *dev_prev = NULL; unsigned long state; int status = 0; + mutex_lock(&video->device_list_lock); - list_for_each_safe(node, next, &video->video_device_list) { + list_for_each(node, &video->video_device_list) { dev = container_of(node, struct acpi_video_device, entry); status = acpi_video_device_get_state(dev, &state); if (state & 0x2) { - dev_next = - container_of(node->next, struct acpi_video_device, - entry); - dev_prev = - container_of(node->prev, struct acpi_video_device, - entry); + dev_next = container_of(node->next, + struct acpi_video_device, entry); + dev_prev = container_of(node->prev, + struct acpi_video_device, entry); goto out; } } + dev_next = container_of(node->next, struct acpi_video_device, entry); dev_prev = container_of(node->prev, struct acpi_video_device, entry); - out: + + out: + mutex_unlock(&video->device_list_lock); + switch (event) { case ACPI_VIDEO_NOTIFY_CYCLE: case ACPI_VIDEO_NOTIFY_NEXT_OUTPUT: @@ -1681,85 +1702,69 @@ 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; - struct list_head *node, *next; - + int error; + struct acpi_device *dev; acpi_video_device_enumerate(video); - list_for_each_safe(node, next, &device->children) { - struct acpi_device *dev = - list_entry(node, struct acpi_device, node); - - if (!dev) - continue; - - status = acpi_video_bus_get_one_device(dev, video); - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Cant attach device")); - continue; - } - + list_for_each_entry(dev, &device->children, node) { + 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; - - down(&video->sem); - list_del(&device->entry); - up(&video->sem); 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); + return 0; } static int acpi_video_bus_put_devices(struct acpi_video_bus *video) { int status; - struct list_head *node, *next; + struct acpi_video_device *dev, *next; + mutex_lock(&video->device_list_lock); - list_for_each_safe(node, next, &video->video_device_list) { - struct acpi_video_device *data = - list_entry(node, struct acpi_video_device, entry); - if (!data) - continue; + list_for_each_entry_safe(dev, next, &video->video_device_list, entry) { - status = acpi_video_bus_put_one_device(data); + status = acpi_video_bus_put_one_device(dev); if (ACPI_FAILURE(status)) printk(KERN_WARNING PREFIX "hhuuhhuu bug in acpi video driver.\n"); - if (data->brightness) - kfree(data->brightness->levels); - kfree(data->brightness); - kfree(data); + if (dev->brightness) { + kfree(dev->brightness->levels); + kfree(dev->brightness); + } + list_del(&dev->entry); + kfree(dev); } + mutex_unlock(&video->device_list_lock); + return 0; } @@ -1778,13 +1783,10 @@ static int acpi_video_bus_stop_devices(s 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; - - printk("video bus notify\n"); - if (!video) return; @@ -1834,14 +1836,12 @@ static void acpi_video_bus_notify(acpi_h 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; @@ -1890,49 +1890,45 @@ static void acpi_video_device_notify(acp 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) { - int result = 0; - acpi_status status = 0; - struct acpi_video_bus *video = NULL; - struct input_dev *input; + static atomic_t instance = ATOMIC_INIT(0); - - if (!device) - return -EINVAL; + 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; acpi_video_bus_find_cap(video); - result = acpi_video_bus_check(video); - if (result) - goto end; - - result = acpi_video_bus_add_fs(device); - if (result) - goto end; - - init_MUTEX(&video->sem); - INIT_LIST_HEAD(&video->video_device_list); + error = acpi_video_bus_check(video); + if (error) + goto err_free_video; + + error = acpi_video_bus_add_fs(device); + if (error) + goto err_free_video; acpi_video_bus_get_devices(video, device); acpi_video_bus_start_devices(video); @@ -1943,16 +1939,15 @@ static int acpi_video_bus_add(struct acp if (ACPI_FAILURE(status)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error installing notify handler\n")); - acpi_video_bus_stop_devices(video); - acpi_video_bus_put_devices(video); - kfree(video->attached_array); - acpi_video_bus_remove_fs(device); - result = -ENODEV; - goto end; + error = -ENODEV; + goto err_stop_video; } - video->input = input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto err_uninstall_notify; + } snprintf(video->phys, sizeof(video->phys), "%s/video/input0", acpi_device_hid(video->device)); @@ -1961,6 +1956,7 @@ static int acpi_video_bus_add(struct acp input->phys = video->phys; input->id.bustype = BUS_HOST; input->id.product = 0x06; + input->dev.parent = &device->dev; input->evbit[0] = BIT(EV_KEY); set_bit(KEY_SWITCHVIDEOMODE, input->keybit); set_bit(KEY_VIDEO_NEXT, input->keybit); @@ -1971,18 +1967,10 @@ static int acpi_video_bus_add(struct acp set_bit(KEY_BRIGHTNESS_ZERO, input->keybit); set_bit(KEY_DISPLAY_OFF, input->keybit); set_bit(KEY_UNKNOWN, input->keybit); - result = input_register_device(input); - if (result) { - acpi_remove_notify_handler(video->device->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_bus_notify); - acpi_video_bus_stop_devices(video); - acpi_video_bus_put_devices(video); - kfree(video->attached_array); - acpi_video_bus_remove_fs(device); - goto end; - } + error = input_register_device(input); + if (error) + goto err_free_input_dev; printk(KERN_INFO PREFIX "%s [%s] (multi-head: %s rom: %s post: %s)\n", ACPI_VIDEO_DEVICE_NAME, acpi_device_bid(device), @@ -1990,34 +1978,41 @@ static int acpi_video_bus_add(struct acp video->flags.rom ? "yes" : "no", video->flags.post ? "yes" : "no"); - end: - if (result) - kfree(video); + return 0; + + err_free_input_dev: + input_free_device(input); + err_uninstall_notify: + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_video_bus_notify); + err_stop_video: + acpi_video_bus_stop_devices(video); + acpi_video_bus_put_devices(video); + kfree(video->attached_array); + acpi_video_bus_remove_fs(device); + err_free_video: + kfree(video); + acpi_driver_data(device) = NULL; - return result; + return error; } 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); @@ -2028,7 +2023,6 @@ static int __init acpi_video_init(void) { int result = 0; - /* acpi_dbg_level = 0xFFFFFFFF; acpi_dbg_layer = 0x08000000; @@ -2050,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 -puN drivers/base/power/Makefile~git-acpi drivers/base/power/Makefile --- a/drivers/base/power/Makefile~git-acpi +++ a/drivers/base/power/Makefile @@ -1,6 +1,6 @@ obj-y := shutdown.o obj-$(CONFIG_PM_SLEEP) += main.o sysfs.o -obj-$(CONFIG_PM_TRACE) += trace.o +obj-$(CONFIG_PM_TRACE_RTC) += trace.o ifeq ($(CONFIG_DEBUG_DRIVER),y) EXTRA_CFLAGS += -DDEBUG diff -puN drivers/cpuidle/Kconfig~git-acpi drivers/cpuidle/Kconfig --- a/drivers/cpuidle/Kconfig~git-acpi +++ a/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 -puN drivers/misc/Kconfig~git-acpi drivers/misc/Kconfig --- a/drivers/misc/Kconfig~git-acpi +++ a/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 -puN drivers/misc/thinkpad_acpi.c~git-acpi drivers/misc/thinkpad_acpi.c --- a/drivers/misc/thinkpad_acpi.c~git-acpi +++ a/drivers/misc/thinkpad_acpi.c @@ -21,11 +21,13 @@ * 02110-1301, USA. */ -#define IBM_VERSION "0.16" -#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))) -#define __unused __attribute__ ((unused)) + +/**************************************************************************** + * Driver-wide structs and misc. variables + */ + +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); + 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 "\\_SB.PCI0.LPC.EC", /* all others */ ); -IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */ -IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */ - - -/************************************************************************* - * Misc ACPI handles - */ +TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */ +TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */ -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(i * 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(stru 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_subdri 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 *pa } 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_se 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 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 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 attrib 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 a 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(cha { 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_d * 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; } -/* sysfs hotkey enable ------------------------------------------------- */ -static ssize_t hotkey_enable_show(struct device *dev, - struct device_attribute *attr, - char *buf) +/* + * Call with hotkey_mutex held + */ +static int hotkey_mask_get(void) { - int res, status; - u32 mask; + u32 m; - res = hotkey_get(&status, &mask); + 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; + + res = hotkey_status_get(&status); if (res) return res; @@ -809,15 +1440,12 @@ static ssize_t hotkey_enable_store(struc 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 d 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 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(stru 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_s 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(s 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_att &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) @@ -964,15 +1702,15 @@ static int __init hotkey_init(struct ibm KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */ KEY_UNKNOWN, /* 0x0D: FN+INSERT */ KEY_UNKNOWN, /* 0x0E: FN+DELETE */ - KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */ + KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */ /* Scan codes 0x10 to 0x1F: Extended ACPI HKEY hot keys */ - KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */ + KEY_RESERVED, /* 0x10: FN+END (brightness down) */ KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ - KEY_VOLUMEUP, /* 0x14: VOLUME UP */ - KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ - KEY_MUTE, /* 0x16: MUTE */ + KEY_RESERVED, /* 0x14: VOLUME UP */ + KEY_RESERVED, /* 0x15: VOLUME DOWN */ + KEY_RESERVED, /* 0x16: MUTE */ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, @@ -993,9 +1731,9 @@ static int __init hotkey_init(struct ibm KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */ - KEY_VOLUMEUP, /* 0x14: VOLUME UP */ - KEY_VOLUMEDOWN, /* 0x15: VOLUME DOWN */ - KEY_MUTE, /* 0x16: MUTE */ + KEY_RESERVED, /* 0x14: VOLUME UP */ + KEY_RESERVED, /* 0x15: VOLUME DOWN */ + KEY_RESERVED, /* 0x16: MUTE */ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */ /* (assignments unknown, please report if found) */ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, @@ -1013,10 +1751,17 @@ static int __init hotkey_init(struct ibm 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 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 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 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 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 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 "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 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_str 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_str 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) { @@ -1342,17 +2114,18 @@ static int hotkey_read(char *p) return len; } - res = mutex_lock_interruptible(&hotkey_mutex); - if (res < 0) - return res; - res = hotkey_get(&status, &mask); + if (mutex_lock_interruptible(&hotkey_mutex)) + return -ERESTARTSYS; + 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 { @@ -1368,18 +2141,15 @@ static int hotkey_write(char *buf) int res, status; u32 mask; char *cmd; - int do_cmd = 0; if (!tp_features.hotkey) return -ENODEV; - res = mutex_lock_interruptible(&hotkey_mutex); - if (res < 0) - return res; + 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))) { @@ -1398,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); @@ -1410,7 +2181,7 @@ errexit: } static const struct acpi_device_id ibm_htk_device_ids[] = { - {IBM_HKEY_HID, 0}, + {TPACPI_ACPI_HKEY_HID, 0}, {"", 0}, }; @@ -1427,6 +2198,7 @@ static struct ibm_struct hotkey_driver_d .write = hotkey_write, .exit = hotkey_exit, .resume = hotkey_resume, + .suspend = hotkey_suspend, .acpi = &ibm_hotkey_acpidriver, }; @@ -1434,6 +2206,16 @@ static struct ibm_struct hotkey_driver_d * 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, @@ -1485,7 +2267,7 @@ static int __init bluetooth_init(struct 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 */ @@ -1598,6 +2380,16 @@ static struct ibm_struct bluetooth_drive * 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, @@ -1649,7 +2441,7 @@ static int __init wan_init(struct ibm_in 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"); @@ -1761,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) { @@ -1779,8 +2595,8 @@ static int __init video_init(struct ibm_ 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 */ @@ -1811,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"); } @@ -1884,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; @@ -1953,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; } @@ -2082,16 +2900,16 @@ static struct ibm_struct video_driver_da * 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; @@ -2169,14 +2987,18 @@ static struct ibm_struct light_driver_da #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}, @@ -2219,7 +3041,7 @@ static int __init dock_init(struct ibm_i { 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)); @@ -2235,7 +3057,7 @@ static int __init dock_init2(struct ibm_ 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", @@ -2267,7 +3089,7 @@ static void dock_notify(struct ibm_struc 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 */ } @@ -2323,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 */ @@ -2342,12 +3165,12 @@ static int __init bay_init(struct ibm_in { 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"); @@ -2476,7 +3299,7 @@ static int __init cmos_init(struct ibm_i 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)); @@ -2540,10 +3363,24 @@ static struct ibm_struct cmos_driver_dat * 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 */ @@ -2551,7 +3388,7 @@ static int __init led_init(struct ibm_in { 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 */ @@ -2640,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 { @@ -2670,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)); @@ -2729,8 +3564,109 @@ static struct ibm_struct beep_driver_dat * 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, @@ -2753,7 +3689,8 @@ static ssize_t thermal_temp_input_show(s } #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), @@ -2847,12 +3784,13 @@ static int __init thermal_init(struct ib 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; @@ -2879,7 +3817,7 @@ static int __init thermal_init(struct ib 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); @@ -2904,7 +3842,7 @@ static int __init thermal_init(struct ib 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); @@ -2921,106 +3859,24 @@ static void thermal_exit(void) } } -/* idx is zero-based */ -static int thermal_get_sensor(int idx, s32 *value) +static int thermal_read(char *p) { - int t; - s8 tmp; - char tmpi[5]; + int len = 0; + int n, i; + struct ibm_thermal_sensors_struct t; - t = TP_EC_THERMAL_TMP0; + n = thermal_get_sensors(&t); + if (unlikely(n < 0)) + return n; - 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; + len += sprintf(p + len, "temperatures:\t"); - 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; - int n, i; - struct ibm_thermal_sensors_struct t; - - n = thermal_get_sensors(&t); - if (unlikely(n < 0)) - return n; - - len += sprintf(p + len, "temperatures:\t"); - - if (n > 0) { - for (i = 0; i < (n - 1); i++) - len += sprintf(p + len, "%d ", t.temp[i] / 1000); - len += sprintf(p + len, "%d\n", t.temp[i] / 1000); - } else - len += sprintf(p + len, "not supported\n"); + if (n > 0) { + for (i = 0; i < (n - 1); i++) + len += sprintf(p + len, "%d ", t.temp[i] / 1000); + len += sprintf(p + len, "%d\n", t.temp[i] / 1000); + } else + len += sprintf(p + len, "not supported\n"); return len; } @@ -3105,74 +3961,15 @@ static struct ibm_struct ecdump_driver_d * Backlight/brightness subdriver */ -static struct backlight_device *ibm_backlight_device; +#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen" -static struct backlight_ops ibm_backlight_data = { - .get_brightness = brightness_get, - .update_status = brightness_update_status, -}; +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; -static int __init brightness_init(struct ibm_init_struct *iibm) -{ - int b; - - vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n"); - - mutex_init(&brightness_mutex); - - if (!brightness_mode) { - if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) - brightness_mode = 2; - else - brightness_mode = 3; - - dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n", - brightness_mode); - } - - if (brightness_mode > 3) - return -EINVAL; - - b = brightness_get(NULL); - if (b < 0) - return 1; - - 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"); - return PTR_ERR(ibm_backlight_device); - } - vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n"); - - ibm_backlight_device->props.max_brightness = 7; - ibm_backlight_device->props.brightness = b; - backlight_update_status(ibm_backlight_device); - - return 0; -} - -static void brightness_exit(void) -{ - if (ibm_backlight_device) { - vdbg_printk(TPACPI_DBG_EXIT, - "calling backlight_device_unregister()\n"); - backlight_device_unregister(ibm_backlight_device); - ibm_backlight_device = NULL; - } -} - -static int brightness_update_status(struct backlight_device *bd) -{ - return brightness_set( - (bd->props.fb_blank == FB_BLANK_UNBLANK && - bd->props.power == FB_BLANK_UNBLANK) ? - bd->props.brightness : 0); -} - /* * ThinkPads can read brightness from two places: EC 0x31, or * CMOS NVRAM byte 0x5E, bits 0-3. @@ -3184,18 +3981,19 @@ static int brightness_get(struct backlig if (brightness_mode & 1) { if (!acpi_ec_read(brightness_offset, &lec)) return -EIO; - lec &= 7; + 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 + printk(TPACPI_ERR "CMOS NVRAM (%u) and EC (%u) do not agree " "on display brightness level\n", (unsigned int) lcmos, @@ -3206,12 +4004,13 @@ static int brightness_get(struct backlig 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 > 7) + if (value > ((tp_features.bright_16levels)? 15 : 7)) return -EINVAL; res = mutex_lock_interruptible(&brightness_mutex); @@ -3227,7 +4026,7 @@ static int brightness_set(int value) cmos_cmd = value > current_value ? TP_CMOS_BRIGHTNESS_UP : TP_CMOS_BRIGHTNESS_DOWN; - inc = value > current_value ? 1 : -1; + inc = (value > current_value)? 1 : -1; res = 0; for (i = current_value; i != value; i += inc) { @@ -3248,18 +4047,206 @@ errout: 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, +}; + +/* --------------------------------------------------------------------- */ + +static int __init tpacpi_query_bcll_levels(acpi_handle handle) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + int rc; + + if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) { + obj = (union acpi_object *)buffer.pointer; + if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) { + printk(TPACPI_ERR "Unknown BCLL data, " + "please report this to %s\n", TPACPI_MAIL); + rc = 0; + } else { + rc = obj->package.count; + } + } else { + return 0; + } + + kfree(buffer.pointer); + return rc; +} + +static acpi_status __init brightness_find_bcll(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + char name[ACPI_PATH_SEGMENT_LENGTH]; + struct acpi_buffer buffer = { sizeof(name), &name }; + + if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) && + !strncmp("BCLL", name, sizeof(name) - 1)) { + if (tpacpi_query_bcll_levels(handle) == 16) { + *rv = handle; + return AE_CTRL_TERMINATE; + } else { + return AE_OK; + } + } else { + return AE_OK; + } +} + +static int __init brightness_check_levels(void) +{ + int status; + void *found_node = NULL; + + if (!vid_handle) { + 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); + + return (ACPI_SUCCESS(status) && found_node != NULL); +} + +static acpi_status __init brightness_find_bcl(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + char name[ACPI_PATH_SEGMENT_LENGTH]; + struct acpi_buffer buffer = { sizeof(name), &name }; + + if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) && + !strncmp("_BCL", name, sizeof(name) - 1)) { + *rv = handle; + return AE_CTRL_TERMINATE; + } else { + return AE_OK; + } +} + +static int __init brightness_check_std_acpi_support(void) +{ + int status; + void *found_node = NULL; + + if (!vid_handle) { + TPACPI_ACPIHANDLE_INIT(vid); + } + if (!vid_handle) + return 0; + + /* Search for a _BCL method, but don't execute it */ + status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3, + brightness_find_bcl, NULL, &found_node); + + return (ACPI_SUCCESS(status) && found_node != NULL); +} + +static int __init brightness_init(struct ibm_init_struct *iibm) +{ + int b; + + vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n"); + + mutex_init(&brightness_mutex); + + if (!brightness_enable) { + dbg_printk(TPACPI_DBG_INIT, + "brightness support disabled by module parameter\n"); + return 1; + } else if (brightness_enable > 1) { + if (brightness_check_std_acpi_support()) { + printk(TPACPI_NOTICE + "standard ACPI backlight interface " + "available, not loading native one...\n"); + return 1; + } + } + + if (!brightness_mode) { + if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) + brightness_mode = 2; + else + brightness_mode = 3; + + dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n", + brightness_mode); + } + + if (brightness_mode > 3) + return -EINVAL; + + tp_features.bright_16levels = + thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO && + brightness_check_levels(); + + b = brightness_get(NULL); + if (b < 0) + return 1; + + if (tp_features.bright_16levels) + 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(TPACPI_ERR "Could not register backlight device\n"); + return PTR_ERR(ibm_backlight_device); + } + vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n"); + + ibm_backlight_device->props.max_brightness = + (tp_features.bright_16levels)? 15 : 7; + ibm_backlight_device->props.brightness = b; + backlight_update_status(ibm_backlight_device); + + return 0; +} + +static void brightness_exit(void) +{ + if (ibm_backlight_device) { + vdbg_printk(TPACPI_DBG_EXIT, + "calling backlight_device_unregister()\n"); + backlight_device_unregister(ibm_backlight_device); + ibm_backlight_device = NULL; + } +} + 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 & 0x7); + len += sprintf(p + len, "level:\t\t%d\n", level); len += sprintf(p + len, "commands:\tup, down\n"); len += sprintf(p + len, "commands:\tlevel " - " ( is 0-7)\n"); + " ( is 0-%d)\n", + (tp_features.bright_16levels) ? 15 : 7); } return len; @@ -3268,28 +4255,34 @@ static int brightness_read(char *p) static int brightness_write(char *buf) { int level; - int new_level; + int rc; char *cmd; + int max_level = (tp_features.bright_16levels) ? 15 : 7; - while ((cmd = next_cmd(&buf))) { - if ((level = brightness_get(NULL)) < 0) - return level; - level &= 7; + level = brightness_get(NULL); + if (level < 0) + return level; + while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "up") == 0) { - new_level = level == 7 ? 7 : level + 1; + if (level < max_level) + level++; } else if (strlencmp(cmd, "down") == 0) { - new_level = level == 0 ? 0 : level - 1; - } else if (sscanf(cmd, "level %d", &new_level) == 1 && - new_level >= 0 && new_level <= 7) { - /* new_level set */ + if (level > 0) + level--; + } else if (sscanf(cmd, "level %d", &level) == 1 && + level >= 0 && level <= max_level) { + /* new level set */ } else return -EINVAL; - - brightness_set(new_level); } - return 0; + /* + * Now we know what the final level should be, so we try to set it. + * Doing it this way makes the syscall restartable in case of EINTR + */ + rc = brightness_set(level); + return (rc == -EINTR)? ERESTARTSYS : rc; } static struct ibm_struct brightness_driver_data = { @@ -3303,6 +4296,8 @@ static struct ibm_struct brightness_driv * Volume subdriver */ +static int volume_offset = 0x30; + static int volume_read(char *p) { int len = 0; @@ -3353,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) || @@ -3365,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)) @@ -3494,463 +4492,357 @@ static struct ibm_struct volume_driver_d * but the ACPI tables just mention level 7. */ -static enum fan_status_access_mode fan_status_access_mode; -static enum fan_control_access_mode fan_control_access_mode; -static enum fan_control_commands fan_control_commands; +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 */ -static u8 fan_control_initial_status; -static u8 fan_control_desired_level; + TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */ + TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */ -static void fan_watchdog_fire(struct work_struct *ignored); -static int fan_watchdog_maxinterval; -static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); + TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */ +}; -IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ -IBM_HANDLE(gfan, ec, "GFAN", /* 570 */ - "\\FSPD", /* 600e/x, 770e, 770x */ - ); /* all others */ -IBM_HANDLE(sfan, ec, "SFAN", /* 570 */ +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 DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire); + +TPACPI_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */ +TPACPI_HANDLE(gfan, ec, "GFAN", /* 570 */ + "\\FSPD", /* 600e/x, 770e, 770x */ + ); /* all others */ +TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */ "JFNS", /* 770x-JL */ ); /* all others */ /* - * SYSFS fan layout: hwmon compatible (device) - * - * pwm*_enable: - * 0: "disengaged" mode - * 1: manual mode - * 2: native EC "auto" mode (recommended, hardware default) - * - * pwm*: set speed in manual mode, ignored otherwise. - * 0 is level 0; 255 is level 7. Intermediate points done with linear - * interpolation. - * - * fan*_input: tachometer reading, RPM - * - * - * SYSFS fan layout: extensions - * - * fan_watchdog (driver): - * fan watchdog interval in seconds, 0 disables (default), max 120 + * Call with fan_mutex held */ - -/* sysfs fan pwm1_enable ----------------------------------------------- */ -static ssize_t fan_pwm1_enable_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static void fan_update_desired_level(u8 status) { - int res, mode; - u8 status; - - res = fan_get_status_safe(&status); - if (res) - return res; - - if (unlikely(tp_features.fan_ctrl_status_undef)) { - if (status != fan_control_initial_status) { - tp_features.fan_ctrl_status_undef = 0; - } else { - /* Return most likely status. In fact, it - * might be the only possible status */ - status = TP_EC_FAN_AUTO; - } + 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; } - - if (status & TP_EC_FAN_FULLSPEED) { - mode = 0; - } else if (status & TP_EC_FAN_AUTO) { - mode = 2; - } else - mode = 1; - - return snprintf(buf, PAGE_SIZE, "%d\n", mode); } -static ssize_t fan_pwm1_enable_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int fan_get_status(u8 *status) { - unsigned long t; - int res, level; + u8 s; - if (parse_strtoul(buf, 2, &t)) - return -EINVAL; + /* TODO: + * Add TPACPI_FAN_RD_ACPI_FANS ? */ - switch (t) { - case 0: - level = TP_EC_FAN_FULLSPEED; - break; - case 1: - level = TPACPI_FAN_LAST_LEVEL; - break; - case 2: - level = TP_EC_FAN_AUTO; - break; - case 3: - /* reserved for software-controlled auto mode */ - return -ENOSYS; - default: - return -EINVAL; - } + switch (fan_status_access_mode) { + case TPACPI_FAN_RD_ACPI_GFAN: + /* 570, 600e/x, 770e, 770x */ - res = fan_set_level_safe(level); - if (res == -ENXIO) - return -EINVAL; - else if (res < 0) - return res; + if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) + return -EIO; - fan_watchdog_reset(); + if (likely(status)) + *status = s & 0x07; - return count; -} + break; -static struct device_attribute dev_attr_fan_pwm1_enable = - __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, - fan_pwm1_enable_show, fan_pwm1_enable_store); + case TPACPI_FAN_RD_TPEC: + /* all except 570, 600e/x, 770e, 770x */ + if (unlikely(!acpi_ec_read(fan_status_offset, &s))) + return -EIO; -/* sysfs fan pwm1 ------------------------------------------------------ */ -static ssize_t fan_pwm1_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int res; - u8 status; + if (likely(status)) + *status = s; - res = fan_get_status_safe(&status); - if (res) - return res; + break; - if (unlikely(tp_features.fan_ctrl_status_undef)) { - if (status != fan_control_initial_status) { - tp_features.fan_ctrl_status_undef = 0; - } else { - status = TP_EC_FAN_AUTO; - } + default: + return -ENXIO; } - if ((status & - (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0) - status = fan_control_desired_level; - - if (status > 7) - status = 7; - - return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7); + return 0; } -static ssize_t fan_pwm1_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int fan_get_status_safe(u8 *status) { - unsigned long s; int rc; - u8 status, newlevel; - - if (parse_strtoul(buf, 255, &s)) - return -EINVAL; - - /* scale down from 0-255 to 0-7 */ - newlevel = (s >> 5) & 0x07; + u8 s; - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; + rc = fan_get_status(&s); + if (!rc) + fan_update_desired_level(s); + mutex_unlock(&fan_mutex); - rc = fan_get_status(&status); - if (!rc && (status & - (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { - rc = fan_set_level(newlevel); - if (rc == -ENXIO) - rc = -EINVAL; - else if (!rc) { - fan_update_desired_level(newlevel); - fan_watchdog_reset(); - } - } + if (status) + *status = s; - mutex_unlock(&fan_mutex); - return (rc)? rc : count; + return rc; } -static struct device_attribute dev_attr_fan_pwm1 = - __ATTR(pwm1, S_IWUSR | S_IRUGO, - fan_pwm1_show, fan_pwm1_store); - -/* sysfs fan fan1_input ------------------------------------------------ */ -static ssize_t fan_fan1_input_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static int fan_get_speed(unsigned int *speed) { - int res; - unsigned int speed; + u8 hi, lo; - res = fan_get_speed(&speed); - if (res < 0) - return res; + 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; - return snprintf(buf, PAGE_SIZE, "%u\n", speed); -} + if (likely(speed)) + *speed = (hi << 8) | lo; -static struct device_attribute dev_attr_fan_fan1_input = - __ATTR(fan1_input, S_IRUGO, - fan_fan1_input_show, NULL); + break; -/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ -static ssize_t fan_fan_watchdog_show(struct device_driver *drv, - char *buf) -{ - return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval); + default: + return -ENXIO; + } + + return 0; } -static ssize_t fan_fan_watchdog_store(struct device_driver *drv, - const char *buf, size_t count) +static int fan_set_level(int level) { - unsigned long t; - - if (parse_strtoul(buf, 120, &t)) - return -EINVAL; - if (!fan_control_allowed) return -EPERM; - fan_watchdog_maxinterval = t; - fan_watchdog_reset(); + 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; - return count; -} + 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; -static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, - fan_fan_watchdog_show, fan_fan_watchdog_store); + /* 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 */ -/* --------------------------------------------------------------------- */ -static struct attribute *fan_attributes[] = { - &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, - &dev_attr_fan_fan1_input.attr, - NULL -}; + if (!acpi_ec_write(fan_status_offset, level)) + return -EIO; + else + tp_features.fan_ctrl_status_undef = 0; + break; -static const struct attribute_group fan_attr_group = { - .attrs = fan_attributes, -}; + default: + return -ENXIO; + } + return 0; +} -static int __init fan_init(struct ibm_init_struct *iibm) +static int fan_set_level_safe(int level) { int rc; - vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); + if (!fan_control_allowed) + return -EPERM; - mutex_init(&fan_mutex); - fan_status_access_mode = TPACPI_FAN_NONE; - fan_control_access_mode = TPACPI_FAN_WR_NONE; - fan_control_commands = 0; - fan_watchdog_maxinterval = 0; - tp_features.fan_ctrl_status_undef = 0; - fan_control_desired_level = 7; + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; - IBM_ACPIHANDLE_INIT(fans); - IBM_ACPIHANDLE_INIT(gfan); - IBM_ACPIHANDLE_INIT(sfan); + if (level == TPACPI_FAN_LAST_LEVEL) + level = fan_control_desired_level; - if (gfan_handle) { - /* 570, 600e/x, 770e, 770x */ - fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; - } else { - /* all other ThinkPads: note that even old-style - * ThinkPad ECs supports the fan control register */ - if (likely(acpi_ec_read(fan_status_offset, - &fan_control_initial_status))) { - fan_status_access_mode = TPACPI_FAN_RD_TPEC; + rc = fan_set_level(level); + if (!rc) + fan_update_desired_level(level); - /* In some ThinkPads, neither the EC nor the ACPI - * DSDT initialize the fan status, and it ends up - * being set to 0x07 when it *could* be either - * 0x07 or 0x80. - * - * Enable for TP-1Y (T43), TP-78 (R51e), - * TP-76 (R52), TP-70 (T43, R52), which are known - * to be buggy. */ - if (fan_control_initial_status == 0x07) { - switch (thinkpad_id.ec_model) { - case 0x5931: /* TP-1Y */ - 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"); - tp_features.fan_ctrl_status_undef = 1; - ;; - } - } - } else { - printk(IBM_ERR - "ThinkPad ACPI EC access misbehaving, " - "fan status and control unavailable\n"); - return 1; - } - } + mutex_unlock(&fan_mutex); + return rc; +} - if (sfan_handle) { - /* 570, 770x-JL */ - fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN; - fan_control_commands |= - TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE; - } else { - if (!gfan_handle) { - /* gfan without sfan means no fan control */ - /* all other models implement TP EC 0x2f control */ +static int fan_set_enable(void) +{ + u8 s; + int rc; - if (fans_handle) { - /* X31, X40, X41 */ - fan_control_access_mode = - TPACPI_FAN_WR_ACPI_FANS; - fan_control_commands |= - TPACPI_FAN_CMD_SPEED | - TPACPI_FAN_CMD_LEVEL | - TPACPI_FAN_CMD_ENABLE; - } else { - fan_control_access_mode = TPACPI_FAN_WR_TPEC; - fan_control_commands |= - TPACPI_FAN_CMD_LEVEL | - TPACPI_FAN_CMD_ENABLE; - } - } - } + if (!fan_control_allowed) + return -EPERM; - vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n", - str_supported(fan_status_access_mode != TPACPI_FAN_NONE || - fan_control_access_mode != TPACPI_FAN_WR_NONE), - fan_status_access_mode, fan_control_access_mode); + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; - /* fan control master switch */ - if (!fan_control_allowed) { - fan_control_access_mode = TPACPI_FAN_WR_NONE; - fan_control_commands = 0; - dbg_printk(TPACPI_DBG_INIT, - "fan control features disabled by parameter\n"); - } + 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; - /* update fan_control_desired_level */ - if (fan_status_access_mode != TPACPI_FAN_NONE) - fan_get_status_safe(NULL); + /* Don't go out of emergency fan mode */ + if (s != 7) { + s &= 0x07; + s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ + } - if (fan_status_access_mode != TPACPI_FAN_NONE || - fan_control_access_mode != TPACPI_FAN_WR_NONE) { - rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, - &fan_attr_group); - if (!(rc < 0)) - rc = driver_create_file(&tpacpi_hwmon_pdriver.driver, - &driver_attr_fan_watchdog); + 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) - return rc; - return 0; - } else - return 1; -} + break; -/* - * 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; + s &= 0x07; + + /* Set fan to at least level 4 */ + s |= 4; + + if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) + rc = -EIO; else - fan_control_desired_level = status; + rc = 0; + break; + + default: + rc = -ENXIO; } + + mutex_unlock(&fan_mutex); + return rc; } -static int fan_get_status(u8 *status) +static int fan_set_disable(void) { - 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 */ + int rc; - if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d"))) - return -EIO; + if (!fan_control_allowed) + return -EPERM; - if (likely(status)) - *status = s & 0x07; + 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_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; - + 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: - return -ENXIO; + rc = -ENXIO; } - return 0; + + mutex_unlock(&fan_mutex); + return rc; } -static int fan_get_status_safe(u8 *status) +static int fan_set_speed(int speed) { int rc; - u8 s; - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; - rc = fan_get_status(&s); - if (!rc) - fan_update_desired_level(s); - mutex_unlock(&fan_mutex); + if (!fan_control_allowed) + return -EPERM; - if (status) - *status = s; + 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_exit(void) -{ - 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); - - cancel_delayed_work(&fan_watchdog_task); - flush_scheduled_work(); -} - -static int fan_get_speed(unsigned int *speed) +static void fan_watchdog_reset(void) { - 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; + static int fan_watchdog_active; - break; + if (fan_control_access_mode == TPACPI_FAN_WR_NONE) + return; - default: - return -ENXIO; - } + if (fan_watchdog_active) + cancel_delayed_work(&fan_watchdog_task); - return 0; + 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) @@ -3960,225 +4852,363 @@ static void fan_watchdog_fire(struct wor if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING) return; - printk(IBM_NOTICE "fan watchdog: enabling fan\n"); + printk(TPACPI_NOTICE "fan watchdog: enabling fan\n"); rc = fan_set_enable(); if (rc < 0) { - printk(IBM_ERR "fan watchdog: error %d while enabling fan, " + printk(TPACPI_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; +/* + * SYSFS fan layout: hwmon compatible (device) + * + * pwm*_enable: + * 0: "disengaged" mode + * 1: manual mode + * 2: native EC "auto" mode (recommended, hardware default) + * + * pwm*: set speed in manual mode, ignored otherwise. + * 0 is level 0; 255 is level 7. Intermediate points done with linear + * interpolation. + * + * fan*_input: tachometer reading, RPM + * + * + * SYSFS fan layout: extensions + * + * fan_watchdog (driver): + * fan watchdog interval in seconds, 0 disables (default), max 120 + */ - if (fan_control_access_mode == TPACPI_FAN_WR_NONE) - return; +/* sysfs fan pwm1_enable ----------------------------------------------- */ +static ssize_t fan_pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res, mode; + u8 status; - if (fan_watchdog_active) - cancel_delayed_work(&fan_watchdog_task); + res = fan_get_status_safe(&status); + if (res) + return res; - 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"); + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + /* Return most likely status. In fact, it + * might be the only possible status */ + status = TP_EC_FAN_AUTO; } + } + + if (status & TP_EC_FAN_FULLSPEED) { + mode = 0; + } else if (status & TP_EC_FAN_AUTO) { + mode = 2; } else - fan_watchdog_active = 0; + mode = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", mode); } -static int fan_set_level(int level) +static ssize_t fan_pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - 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; + unsigned long t; + int res, level; - /* 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 (parse_strtoul(buf, 2, &t)) + return -EINVAL; - if (!acpi_ec_write(fan_status_offset, level)) - return -EIO; - else - tp_features.fan_ctrl_status_undef = 0; + switch (t) { + case 0: + level = TP_EC_FAN_FULLSPEED; break; - + case 1: + level = TPACPI_FAN_LAST_LEVEL; + break; + case 2: + level = TP_EC_FAN_AUTO; + break; + case 3: + /* reserved for software-controlled auto mode */ + return -ENOSYS; default: - return -ENXIO; + return -EINVAL; } - return 0; + + res = fan_set_level_safe(level); + if (res == -ENXIO) + return -EINVAL; + else if (res < 0) + return res; + + fan_watchdog_reset(); + + return count; } -static int fan_set_level_safe(int level) +static struct device_attribute dev_attr_fan_pwm1_enable = + __ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + fan_pwm1_enable_show, fan_pwm1_enable_store); + +/* sysfs fan pwm1 ------------------------------------------------------ */ +static ssize_t fan_pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - int rc; + int res; + u8 status; - if (!fan_control_allowed) - return -EPERM; + res = fan_get_status_safe(&status); + if (res) + return res; - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; + if (unlikely(tp_features.fan_ctrl_status_undef)) { + if (status != fan_control_initial_status) { + tp_features.fan_ctrl_status_undef = 0; + } else { + status = TP_EC_FAN_AUTO; + } + } - if (level == TPACPI_FAN_LAST_LEVEL) - level = fan_control_desired_level; + if ((status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0) + status = fan_control_desired_level; - rc = fan_set_level(level); - if (!rc) - fan_update_desired_level(level); + if (status > 7) + status = 7; - mutex_unlock(&fan_mutex); - return rc; + return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7); } -static int fan_set_enable(void) +static ssize_t fan_pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { - u8 s; + unsigned long s; int rc; + u8 status, newlevel; - if (!fan_control_allowed) - return -EPERM; + if (parse_strtoul(buf, 255, &s)) + return -EINVAL; - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; + /* scale down from 0-255 to 0-7 */ + newlevel = (s >> 5) & 0x07; - 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; + if (mutex_lock_interruptible(&fan_mutex)) + return -ERESTARTSYS; - /* Don't go out of emergency fan mode */ - if (s != 7) { - s &= 0x07; - s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */ + rc = fan_get_status(&status); + if (!rc && (status & + (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) { + rc = fan_set_level(newlevel); + if (rc == -ENXIO) + rc = -EINVAL; + else if (!rc) { + fan_update_desired_level(newlevel); + fan_watchdog_reset(); } + } - if (!acpi_ec_write(fan_status_offset, s)) - rc = -EIO; - else { - tp_features.fan_ctrl_status_undef = 0; - rc = 0; - } - break; + mutex_unlock(&fan_mutex); + return (rc)? rc : count; +} - case TPACPI_FAN_WR_ACPI_SFAN: - rc = fan_get_status(&s); - if (rc < 0) - break; +static struct device_attribute dev_attr_fan_pwm1 = + __ATTR(pwm1, S_IWUSR | S_IRUGO, + fan_pwm1_show, fan_pwm1_store); - s &= 0x07; +/* sysfs fan fan1_input ------------------------------------------------ */ +static ssize_t fan_fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res; + unsigned int speed; - /* Set fan to at least level 4 */ - s |= 4; + res = fan_get_speed(&speed); + if (res < 0) + return res; - if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s)) - rc= -EIO; - else - rc = 0; - break; + return snprintf(buf, PAGE_SIZE, "%u\n", speed); +} - default: - rc = -ENXIO; - } +static struct device_attribute dev_attr_fan_fan1_input = + __ATTR(fan1_input, S_IRUGO, + fan_fan1_input_show, NULL); - mutex_unlock(&fan_mutex); - return rc; +/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */ +static ssize_t fan_fan_watchdog_show(struct device_driver *drv, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval); } -static int fan_set_disable(void) +static ssize_t fan_fan_watchdog_store(struct device_driver *drv, + const char *buf, size_t count) { - int rc; + unsigned long t; + + if (parse_strtoul(buf, 120, &t)) + return -EINVAL; if (!fan_control_allowed) return -EPERM; - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; + fan_watchdog_maxinterval = t; + fan_watchdog_reset(); - 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; + return count; +} + +static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO, + fan_fan_watchdog_show, fan_fan_watchdog_store); + +/* --------------------------------------------------------------------- */ +static struct attribute *fan_attributes[] = { + &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr, + &dev_attr_fan_fan1_input.attr, + NULL +}; + +static const struct attribute_group fan_attr_group = { + .attrs = fan_attributes, +}; + +static int __init fan_init(struct ibm_init_struct *iibm) +{ + int rc; + + vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n"); + + mutex_init(&fan_mutex); + fan_status_access_mode = TPACPI_FAN_NONE; + fan_control_access_mode = TPACPI_FAN_WR_NONE; + fan_control_commands = 0; + fan_watchdog_maxinterval = 0; + tp_features.fan_ctrl_status_undef = 0; + fan_control_desired_level = 7; + + TPACPI_ACPIHANDLE_INIT(fans); + TPACPI_ACPIHANDLE_INIT(gfan); + TPACPI_ACPIHANDLE_INIT(sfan); + + if (gfan_handle) { + /* 570, 600e/x, 770e, 770x */ + fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN; + } else { + /* all other ThinkPads: note that even old-style + * ThinkPad ECs supports the fan control register */ + if (likely(acpi_ec_read(fan_status_offset, + &fan_control_initial_status))) { + fan_status_access_mode = TPACPI_FAN_RD_TPEC; + + /* In some ThinkPads, neither the EC nor the ACPI + * DSDT initialize the fan status, and it ends up + * being set to 0x07 when it *could* be either + * 0x07 or 0x80. + * + * Enable for TP-1Y (T43), TP-78 (R51e), + * TP-76 (R52), TP-70 (T43, R52), which are known + * to be buggy. */ + if (fan_control_initial_status == 0x07) { + switch (thinkpad_id.ec_model) { + case 0x5931: /* TP-1Y */ + case 0x3837: /* TP-78 */ + case 0x3637: /* TP-76 */ + case 0x3037: /* TP-70 */ + 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(TPACPI_ERR + "ThinkPad ACPI EC access misbehaving, " + "fan status and control unavailable\n"); + return 1; } - 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; + if (sfan_handle) { + /* 570, 770x-JL */ + fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN; + fan_control_commands |= + TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE; + } else { + if (!gfan_handle) { + /* gfan without sfan means no fan control */ + /* all other models implement TP EC 0x2f control */ - default: - rc = -ENXIO; + if (fans_handle) { + /* X31, X40, X41 */ + fan_control_access_mode = + TPACPI_FAN_WR_ACPI_FANS; + fan_control_commands |= + TPACPI_FAN_CMD_SPEED | + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; + } else { + fan_control_access_mode = TPACPI_FAN_WR_TPEC; + fan_control_commands |= + TPACPI_FAN_CMD_LEVEL | + TPACPI_FAN_CMD_ENABLE; + } + } } + vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n", + str_supported(fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE), + fan_status_access_mode, fan_control_access_mode); - mutex_unlock(&fan_mutex); - return rc; + /* fan control master switch */ + if (!fan_control_allowed) { + fan_control_access_mode = TPACPI_FAN_WR_NONE; + fan_control_commands = 0; + dbg_printk(TPACPI_DBG_INIT, + "fan control features disabled by parameter\n"); + } + + /* update fan_control_desired_level */ + if (fan_status_access_mode != TPACPI_FAN_NONE) + fan_get_status_safe(NULL); + + if (fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE) { + rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj, + &fan_attr_group); + if (!(rc < 0)) + rc = driver_create_file(&tpacpi_hwmon_pdriver.driver, + &driver_attr_fan_watchdog); + if (rc < 0) + return rc; + return 0; + } else + return 1; } -static int fan_set_speed(int speed) +static void fan_exit(void) { - int rc; - - if (!fan_control_allowed) - return -EPERM; - - rc = mutex_lock_interruptible(&fan_mutex); - if (rc < 0) - return rc; - - 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; + vdbg_printk(TPACPI_DBG_EXIT, + "cancelling any pending fan watchdog tasks\n"); - default: - rc = -ENXIO; - } + /* 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); - mutex_unlock(&fan_mutex); - return rc; + cancel_delayed_work(&fan_watchdog_task); + flush_scheduled_work(); } static int fan_read(char *p) @@ -4191,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" @@ -4201,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)) { @@ -4216,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); @@ -4252,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 " @@ -4274,8 +5307,9 @@ static int fan_write_cmd_level(const cha 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; @@ -4286,8 +5320,9 @@ static int fan_write_cmd_enable(const ch 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; @@ -4298,8 +5333,9 @@ static int fan_write_cmd_disable(const c 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; @@ -4315,8 +5351,9 @@ static int fan_write_cmd_speed(const cha 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; @@ -4380,7 +5417,7 @@ static ssize_t thinkpad_acpi_pdev_name_s 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 = @@ -4391,14 +5428,12 @@ static struct device_attribute dev_attr_ /* /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) { @@ -4408,6 +5443,48 @@ static const char * __init str_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; @@ -4444,7 +5521,7 @@ static int __init ibm_init(struct ibm_in 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; @@ -4462,7 +5539,7 @@ static int __init ibm_init(struct ibm_in 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; @@ -4488,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) @@ -4599,10 +5634,10 @@ static int __init probe_for_thinkpad(voi 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; } @@ -4701,9 +5736,15 @@ static int __init set_ibm_param(const ch unsigned int i; struct ibm_struct *ibm; + if (!kp || !kp->name || !val) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { ibm = ibms_init[i].data; - BUG_ON(ibm == NULL); + WARN_ON(ibm == NULL); + + if (!ibm || !ibm->name) + continue; if (strcmp(ibm->name, kp->name) == 0 && ibm->write) { if (strlen(val) > sizeof(ibms_init[i].param) - 2) @@ -4717,44 +5758,110 @@ static int __init set_ibm_param(const ch 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"); + +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) { @@ -4777,12 +5884,13 @@ static int __init thinkpad_acpi_module_i /* 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; } @@ -4790,7 +5898,8 @@ static int __init thinkpad_acpi_module_i 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; } @@ -4798,7 +5907,8 @@ static int __init thinkpad_acpi_module_i 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; } @@ -4807,10 +5917,12 @@ static int __init thinkpad_acpi_module_i 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; } @@ -4818,30 +5930,31 @@ static int __init thinkpad_acpi_module_i /* 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; } @@ -4850,20 +5963,20 @@ static int __init thinkpad_acpi_module_i 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 : @@ -4882,7 +5995,7 @@ static int __init thinkpad_acpi_module_i } 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 { @@ -4893,56 +6006,36 @@ static int __init thinkpad_acpi_module_i 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 -puN drivers/misc/thinkpad_acpi.h~git-acpi /dev/null --- a/drivers/misc/thinkpad_acpi.h +++ /dev/null @@ -1,604 +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 0x07 -#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 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 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 -puN drivers/pnp/pnpacpi/rsparser.c~git-acpi drivers/pnp/pnpacpi/rsparser.c --- a/drivers/pnp/pnpacpi/rsparser.c~git-acpi +++ a/drivers/pnp/pnpacpi/rsparser.c @@ -75,6 +75,7 @@ static void pnpacpi_parse_allocated_irqr { int i = 0; int irq; + int p, t; if (!valid_IRQ(gsi)) return; diff -puN include/acpi/acpi_drivers.h~git-acpi include/acpi/acpi_drivers.h --- a/include/acpi/acpi_drivers.h~git-acpi +++ a/include/acpi/acpi_drivers.h @@ -73,7 +73,6 @@ struct pci_bus; acpi_status acpi_get_pci_id(acpi_handle handle, struct acpi_pci_id *id); int acpi_pci_bind(struct acpi_device *device); -int acpi_pci_unbind(struct acpi_device *device); int acpi_pci_bind_root(struct acpi_device *device, struct acpi_pci_id *id, struct pci_bus *bus); diff -puN include/acpi/acpi_numa.h~git-acpi include/acpi/acpi_numa.h --- a/include/acpi/acpi_numa.h~git-acpi +++ a/include/acpi/acpi_numa.h @@ -15,7 +15,6 @@ extern int pxm_to_node(int); extern int node_to_pxm(int); extern void __acpi_map_pxm_to_node(int, int); extern int acpi_map_pxm_to_node(int); -extern void __cpuinit acpi_unmap_pxm_to_node(int); #endif /* CONFIG_ACPI_NUMA */ #endif /* __ACP_NUMA_H */ diff -puN include/acpi/processor.h~git-acpi include/acpi/processor.h --- a/include/acpi/processor.h~git-acpi +++ a/include/acpi/processor.h @@ -78,7 +78,6 @@ struct acpi_processor_cx { struct acpi_processor_power { struct cpuidle_device dev; struct acpi_processor_cx *state; - struct acpi_processor_cx *bm_state; unsigned long bm_check_timestamp; u32 default_state; u32 bm_activity; diff -puN include/linux/Kbuild~git-acpi include/linux/Kbuild --- a/include/linux/Kbuild~git-acpi +++ a/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 -puN include/linux/acpi.h~git-acpi include/linux/acpi.h --- a/include/linux/acpi.h~git-acpi +++ a/include/linux/acpi.h @@ -132,6 +132,11 @@ extern unsigned long acpi_realmode_flags int acpi_register_gsi (u32 gsi, int triggering, int polarity); int acpi_gsi_to_irq (u32 gsi, unsigned int *irq); +#ifdef CONFIG_X86_IO_APIC +extern int acpi_get_override_irq(int bus_irq, int *trigger, int *polarity); +#else +#define acpi_get_override_irq(bus, trigger, polarity) (-1) +#endif /* * This function undoes the effect of one call to acpi_register_gsi(). * If this matches the last registration, any IRQ resources for gsi diff -puN include/linux/cpuidle.h~git-acpi include/linux/cpuidle.h --- a/include/linux/cpuidle.h~git-acpi +++ a/include/linux/cpuidle.h @@ -92,6 +92,7 @@ struct cpuidle_device { struct kobject kobj; struct completion kobj_unregister; void *governor_data; + struct cpuidle_state *safe_state; }; DECLARE_PER_CPU(struct cpuidle_device *, cpuidle_devices); diff -puN include/linux/notifier.h~git-acpi include/linux/notifier.h --- a/include/linux/notifier.h~git-acpi +++ a/include/linux/notifier.h @@ -230,6 +230,8 @@ static inline int notifier_to_errno(int #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 -puN include/linux/suspend.h~git-acpi include/linux/suspend.h --- a/include/linux/suspend.h~git-acpi +++ a/include/linux/suspend.h @@ -216,17 +216,8 @@ void __save_processor_state(struct saved 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 -puN /dev/null include/linux/suspend_ioctls.h --- /dev/null +++ a/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 -puN kernel/power/Kconfig~git-acpi kernel/power/Kconfig --- a/kernel/power/Kconfig~git-acpi +++ a/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 diff -puN kernel/power/disk.c~git-acpi kernel/power/disk.c --- a/kernel/power/disk.c~git-acpi +++ a/kernel/power/disk.c @@ -70,6 +70,35 @@ void hibernation_set_ops(struct platform 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 @@ -167,6 +196,10 @@ int create_image(int platform_mode) goto Enable_irqs; } + if (hibernation_test(TEST_CORE)) + goto Power_up; + + in_suspend = 1; save_processor_state(); error = swsusp_arch_suspend(); if (error) @@ -175,6 +208,7 @@ int create_image(int platform_mode) 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 +245,29 @@ int hibernation_snapshot(int platform_mo 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(); @@ -406,11 +445,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; @@ -499,6 +539,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; @@ -522,6 +566,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: diff -puN kernel/power/main.c~git-acpi kernel/power/main.c --- a/kernel/power/main.c~git-acpi +++ a/kernel/power/main.c @@ -24,10 +24,108 @@ #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_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 */ + + #ifdef CONFIG_SUSPEND /* This is just an arbitrary number */ @@ -133,7 +231,10 @@ static int suspend_enter(suspend_state_t printk(KERN_ERR "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(); @@ -164,16 +265,25 @@ int suspend_devices_and_enter(suspend_st printk(KERN_ERR "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: @@ -240,12 +350,17 @@ static int enter_state(suspend_state_t s 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 +478,18 @@ pm_trace_store(struct kset *kset, const } 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 -puN kernel/power/power.h~git-acpi kernel/power/power.h --- a/kernel/power/power.h~git-acpi +++ a/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; @@ -203,11 +174,35 @@ static inline int suspend_devices_and_en } #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 -puN kernel/power/snapshot.c~git-acpi kernel/power/snapshot.c --- a/kernel/power/snapshot.c~git-acpi +++ a/kernel/power/snapshot.c @@ -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 */ /** @@ -1264,12 +1263,17 @@ static char *check_image_kernel(struct s } #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); diff -puN kernel/power/swsusp.c~git-acpi kernel/power/swsusp.c --- a/kernel/power/swsusp.c~git-acpi +++ a/kernel/power/swsusp.c @@ -64,14 +64,6 @@ unsigned long image_size = 500 * 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. diff -puN kernel/power/user.c~git-acpi kernel/power/user.c --- a/kernel/power/user.c~git-acpi +++ a/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 = ATO 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 *i 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 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 * { 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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: _