2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2007-03-29 12:58:43 +08:00
|
|
|
* thinkpad_acpi.c - ThinkPad ACPI Extras
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
*
|
2005-08-17 12:00:00 +08:00
|
|
|
* Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
|
2009-04-04 12:25:42 +08:00
|
|
|
* Copyright (C) 2006-2009 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* 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
|
2007-03-24 04:33:58 +08:00
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301, USA.
|
2005-08-17 12:00:00 +08:00
|
|
|
*/
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2009-12-16 07:51:13 +08:00
|
|
|
#define TPACPI_VERSION "0.24"
|
2009-12-16 07:51:09 +08:00
|
|
|
#define TPACPI_SYSFS_VERSION 0x020700
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
/*
|
2005-04-17 06:20:36 +08:00
|
|
|
* Changelog:
|
2008-01-08 23:02:46 +08:00
|
|
|
* 2007-10-20 changelog trimmed down
|
|
|
|
*
|
2007-03-29 12:58:43 +08:00
|
|
|
* 2007-03-27 0.14 renamed to thinkpad_acpi and moved to
|
|
|
|
* drivers/misc.
|
2006-11-26 02:37:38 +08:00
|
|
|
*
|
|
|
|
* 2006-11-22 0.13 new maintainer
|
|
|
|
* changelog now lives in git commit history, and will
|
|
|
|
* not be updated further in-file.
|
2007-03-24 04:33:54 +08:00
|
|
|
*
|
2005-08-17 12:00:00 +08:00
|
|
|
* 2005-03-17 0.11 support for 600e, 770x
|
|
|
|
* thanks to Jamie Lentin <lentinj@dial.pipex.com>
|
2008-01-08 23:02:46 +08:00
|
|
|
*
|
|
|
|
* 2005-01-16 0.9 use MODULE_VERSION
|
2005-08-17 12:00:00 +08:00
|
|
|
* thanks to Henrik Brix Andersen <brix@gentoo.org>
|
|
|
|
* fix parameter passing on module loading
|
|
|
|
* thanks to Rusty Russell <rusty@rustcorp.com.au>
|
|
|
|
* thanks to Jim Radford <radford@blackbean.org>
|
|
|
|
* 2004-11-08 0.8 fix init error case, don't return from a macro
|
|
|
|
* thanks to Chris Wright <chrisw@osdl.org>
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:43 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/mutex.h>
|
2009-04-04 12:25:47 +08:00
|
|
|
#include <linux/sched.h>
|
2008-01-08 23:02:43 +08:00
|
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/freezer.h>
|
|
|
|
#include <linux/delay.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2008-01-08 23:02:43 +08:00
|
|
|
|
|
|
|
#include <linux/nvram.h>
|
|
|
|
#include <linux/proc_fs.h>
|
2009-12-16 07:51:12 +08:00
|
|
|
#include <linux/seq_file.h>
|
2008-01-08 23:02:43 +08:00
|
|
|
#include <linux/sysfs.h>
|
|
|
|
#include <linux/backlight.h>
|
|
|
|
#include <linux/fb.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/hwmon.h>
|
|
|
|
#include <linux/hwmon-sysfs.h>
|
|
|
|
#include <linux/input.h>
|
2008-04-26 12:02:23 +08:00
|
|
|
#include <linux/leds.h>
|
2008-07-21 20:15:51 +08:00
|
|
|
#include <linux/rfkill.h>
|
2008-01-08 23:02:43 +08:00
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/control.h>
|
|
|
|
#include <sound/initval.h>
|
|
|
|
|
2008-01-08 23:02:43 +08:00
|
|
|
#include <acpi/acpi_drivers.h>
|
|
|
|
|
|
|
|
#include <linux/pci_ids.h>
|
|
|
|
|
|
|
|
|
|
|
|
/* 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
|
2008-04-26 12:02:23 +08:00
|
|
|
#define TP_CMOS_THINKLIGHT_ON 12
|
|
|
|
#define TP_CMOS_THINKLIGHT_OFF 13
|
2008-01-08 23:02:43 +08:00
|
|
|
|
|
|
|
/* 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,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* 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,
|
|
|
|
};
|
|
|
|
|
2010-05-17 06:45:28 +08:00
|
|
|
/* Misc NVRAM-related */
|
|
|
|
enum {
|
|
|
|
TP_NVRAM_LEVEL_VOLUME_MAX = 14,
|
|
|
|
};
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* ACPI HIDs */
|
2011-05-09 06:04:29 +08:00
|
|
|
#define TPACPI_ACPI_IBM_HKEY_HID "IBM0068"
|
|
|
|
#define TPACPI_ACPI_LENOVO_HKEY_HID "LEN0068"
|
2010-05-17 06:45:43 +08:00
|
|
|
#define TPACPI_ACPI_EC_HID "PNP0C09"
|
2008-01-08 23:02:45 +08:00
|
|
|
|
|
|
|
/* Input IDs */
|
|
|
|
#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
|
|
|
|
#define TPACPI_HKEY_INPUT_VERSION 0x4101
|
|
|
|
|
2009-01-11 13:01:01 +08:00
|
|
|
/* ACPI \WGSV commands */
|
|
|
|
enum {
|
|
|
|
TP_ACPI_WGSV_GET_STATE = 0x01, /* Get state information */
|
|
|
|
TP_ACPI_WGSV_PWR_ON_ON_RESUME = 0x02, /* Resume WWAN powered on */
|
|
|
|
TP_ACPI_WGSV_PWR_OFF_ON_RESUME = 0x03, /* Resume WWAN powered off */
|
|
|
|
TP_ACPI_WGSV_SAVE_STATE = 0x04, /* Save state for S4/S5 */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* TP_ACPI_WGSV_GET_STATE bits */
|
|
|
|
enum {
|
|
|
|
TP_ACPI_WGSV_STATE_WWANEXIST = 0x0001, /* WWAN hw available */
|
|
|
|
TP_ACPI_WGSV_STATE_WWANPWR = 0x0002, /* WWAN radio enabled */
|
|
|
|
TP_ACPI_WGSV_STATE_WWANPWRRES = 0x0004, /* WWAN state at resume */
|
|
|
|
TP_ACPI_WGSV_STATE_WWANBIOSOFF = 0x0008, /* WWAN disabled in BIOS */
|
|
|
|
TP_ACPI_WGSV_STATE_BLTHEXIST = 0x0001, /* BLTH hw available */
|
|
|
|
TP_ACPI_WGSV_STATE_BLTHPWR = 0x0002, /* BLTH radio enabled */
|
|
|
|
TP_ACPI_WGSV_STATE_BLTHPWRRES = 0x0004, /* BLTH state at resume */
|
|
|
|
TP_ACPI_WGSV_STATE_BLTHBIOSOFF = 0x0008, /* BLTH disabled in BIOS */
|
|
|
|
TP_ACPI_WGSV_STATE_UWBEXIST = 0x0010, /* UWB hw available */
|
|
|
|
TP_ACPI_WGSV_STATE_UWBPWR = 0x0020, /* UWB radio enabled */
|
|
|
|
};
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
/* HKEY events */
|
|
|
|
enum tpacpi_hkey_event_t {
|
|
|
|
/* Hotkey-related */
|
|
|
|
TP_HKEY_EV_HOTKEY_BASE = 0x1001, /* first hotkey (FN+F1) */
|
|
|
|
TP_HKEY_EV_BRGHT_UP = 0x1010, /* Brightness up */
|
|
|
|
TP_HKEY_EV_BRGHT_DOWN = 0x1011, /* Brightness down */
|
|
|
|
TP_HKEY_EV_VOL_UP = 0x1015, /* Volume up or unmute */
|
|
|
|
TP_HKEY_EV_VOL_DOWN = 0x1016, /* Volume down or unmute */
|
|
|
|
TP_HKEY_EV_VOL_MUTE = 0x1017, /* Mixer output mute */
|
|
|
|
|
|
|
|
/* Reasons for waking up from S3/S4 */
|
|
|
|
TP_HKEY_EV_WKUP_S3_UNDOCK = 0x2304, /* undock requested, S3 */
|
|
|
|
TP_HKEY_EV_WKUP_S4_UNDOCK = 0x2404, /* undock requested, S4 */
|
|
|
|
TP_HKEY_EV_WKUP_S3_BAYEJ = 0x2305, /* bay ejection req, S3 */
|
|
|
|
TP_HKEY_EV_WKUP_S4_BAYEJ = 0x2405, /* bay ejection req, S4 */
|
|
|
|
TP_HKEY_EV_WKUP_S3_BATLOW = 0x2313, /* battery empty, S3 */
|
|
|
|
TP_HKEY_EV_WKUP_S4_BATLOW = 0x2413, /* battery empty, S4 */
|
|
|
|
|
|
|
|
/* Auto-sleep after eject request */
|
|
|
|
TP_HKEY_EV_BAYEJ_ACK = 0x3003, /* bay ejection complete */
|
|
|
|
TP_HKEY_EV_UNDOCK_ACK = 0x4003, /* undock complete */
|
|
|
|
|
|
|
|
/* Misc bay events */
|
|
|
|
TP_HKEY_EV_OPTDRV_EJ = 0x3006, /* opt. drive tray ejected */
|
2011-06-06 03:22:35 +08:00
|
|
|
TP_HKEY_EV_HOTPLUG_DOCK = 0x4010, /* docked into hotplug dock
|
|
|
|
or port replicator */
|
|
|
|
TP_HKEY_EV_HOTPLUG_UNDOCK = 0x4011, /* undocked from hotplug
|
|
|
|
dock or port replicator */
|
2009-09-21 01:09:27 +08:00
|
|
|
|
|
|
|
/* User-interface events */
|
|
|
|
TP_HKEY_EV_LID_CLOSE = 0x5001, /* laptop lid closed */
|
|
|
|
TP_HKEY_EV_LID_OPEN = 0x5002, /* laptop lid opened */
|
|
|
|
TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */
|
|
|
|
TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */
|
|
|
|
TP_HKEY_EV_PEN_INSERTED = 0x500b, /* tablet pen inserted */
|
|
|
|
TP_HKEY_EV_PEN_REMOVED = 0x500c, /* tablet pen removed */
|
|
|
|
TP_HKEY_EV_BRGHT_CHANGED = 0x5010, /* backlight control event */
|
|
|
|
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
/* Key-related user-interface events */
|
|
|
|
TP_HKEY_EV_KEY_NUMLOCK = 0x6000, /* NumLock key pressed */
|
|
|
|
TP_HKEY_EV_KEY_FN = 0x6005, /* Fn key pressed? E420 */
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
/* Thermal events */
|
|
|
|
TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */
|
|
|
|
TP_HKEY_EV_ALARM_BAT_XHOT = 0x6012, /* battery critically hot */
|
|
|
|
TP_HKEY_EV_ALARM_SENSOR_HOT = 0x6021, /* sensor too hot */
|
|
|
|
TP_HKEY_EV_ALARM_SENSOR_XHOT = 0x6022, /* sensor critically hot */
|
|
|
|
TP_HKEY_EV_THM_TABLE_CHANGED = 0x6030, /* thermal table changed */
|
|
|
|
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
TP_HKEY_EV_UNK_6040 = 0x6040, /* Related to AC change?
|
|
|
|
some sort of APM hint,
|
|
|
|
W520 */
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
/* Misc */
|
|
|
|
TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */
|
|
|
|
};
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/****************************************************************************
|
|
|
|
* Main driver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
#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"
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
#define TPACPI_PROC_DIR "ibm"
|
|
|
|
#define TPACPI_ACPI_EVENT_PREFIX "ibm"
|
|
|
|
#define TPACPI_DRVR_NAME TPACPI_FILE
|
2008-04-26 12:02:22 +08:00
|
|
|
#define TPACPI_DRVR_SHORTNAME "tpacpi"
|
2008-01-08 23:02:48 +08:00
|
|
|
#define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon"
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2008-04-26 12:02:22 +08:00
|
|
|
#define TPACPI_NVRAM_KTHREAD_NAME "ktpacpi_nvramd"
|
2008-04-26 12:02:28 +08:00
|
|
|
#define TPACPI_WORKQUEUE_NAME "ktpacpid"
|
2008-04-26 12:02:22 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
#define TPACPI_MAX_ACPI_ARGS 3
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2009-04-04 12:25:46 +08:00
|
|
|
/* Debugging printk groups */
|
2008-01-08 23:02:43 +08:00
|
|
|
#define TPACPI_DBG_ALL 0xffff
|
2009-04-04 12:25:47 +08:00
|
|
|
#define TPACPI_DBG_DISCLOSETASK 0x8000
|
2008-01-08 23:02:43 +08:00
|
|
|
#define TPACPI_DBG_INIT 0x0001
|
|
|
|
#define TPACPI_DBG_EXIT 0x0002
|
2009-04-04 12:25:50 +08:00
|
|
|
#define TPACPI_DBG_RFKILL 0x0004
|
2009-04-04 12:25:51 +08:00
|
|
|
#define TPACPI_DBG_HKEY 0x0008
|
2009-04-04 12:25:52 +08:00
|
|
|
#define TPACPI_DBG_FAN 0x0010
|
2009-04-04 12:25:53 +08:00
|
|
|
#define TPACPI_DBG_BRGHT 0x0020
|
2009-12-16 07:51:08 +08:00
|
|
|
#define TPACPI_DBG_MIXER 0x0040
|
2008-01-08 23:02:43 +08:00
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
#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)))
|
2008-01-08 23:02:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
2008-01-08 23:02:45 +08:00
|
|
|
* Driver-wide structs and misc. variables
|
2008-01-08 23:02:43 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
int (*read) (struct seq_file *);
|
2008-01-08 23:02:43 +08:00
|
|
|
int (*write) (char *);
|
|
|
|
void (*exit) (void);
|
|
|
|
void (*resume) (void);
|
2012-06-28 05:18:44 +08:00
|
|
|
void (*suspend) (void);
|
2009-01-11 13:01:02 +08:00
|
|
|
void (*shutdown) (void);
|
2008-01-08 23:02:43 +08:00
|
|
|
|
|
|
|
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 *);
|
2011-07-24 15:36:29 +08:00
|
|
|
umode_t base_procfs_mode;
|
2008-01-08 23:02:43 +08:00
|
|
|
struct ibm_struct *data;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
u32 bluetooth:1;
|
|
|
|
u32 hotkey:1;
|
|
|
|
u32 hotkey_mask:1;
|
|
|
|
u32 hotkey_wlsw:1;
|
2008-02-16 12:17:58 +08:00
|
|
|
u32 hotkey_tablet:1;
|
2008-01-08 23:02:43 +08:00
|
|
|
u32 light:1;
|
|
|
|
u32 light_status:1;
|
2008-04-26 12:02:17 +08:00
|
|
|
u32 bright_acpimode:1;
|
2010-05-17 06:45:33 +08:00
|
|
|
u32 bright_unkfw:1;
|
2008-01-08 23:02:43 +08:00
|
|
|
u32 wan:1;
|
2009-01-11 13:01:03 +08:00
|
|
|
u32 uwb:1;
|
2008-01-08 23:02:43 +08:00
|
|
|
u32 fan_ctrl_status_undef:1;
|
2009-06-18 11:40:17 +08:00
|
|
|
u32 second_fan:1;
|
2009-05-31 00:25:07 +08:00
|
|
|
u32 beep_needs_two_args:1;
|
2009-12-16 07:51:09 +08:00
|
|
|
u32 mixer_no_level_control:1;
|
2008-01-08 23:02:43 +08:00
|
|
|
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;
|
|
|
|
|
ACPI: thinkpad-acpi: warn once about weird hotkey masks
thinkpad-acpi knows for a while now how to best program the hotkeys by
default, and always enable them by default. Unfortunately, this
information has not filtered down everywhere it needs to, yet. Notably,
old ibm-acpi documentation and most "thinkpad setup guides" will have wrong
information on this area.
Warn the local admin once whenever any of the following patterns are met:
1. Attempts to set hotkey mask to 0xffff (artifact from docs and config
for the old ibm-acpi driver and behaviour). This mask makes no
real-world sense;
2. Attempts to set hotkey mask to 0xffffffff, which means the user is
trying to just have "everything work" without even reading the
documentation, or that we need to get a bug report, because there
is a new thinkpad out there with new exciting hot keys :-)
3. Attempts to set hotkey mask to 0xffffff, which is almost never the
correct way to set up volume and brightness event reporting (and with
the current state-of-the-art, it is known to never be right way to do
it).
The driver will perform any and all requested operations, though,
regardless of any warnings. I hope these warnings can be removed one or
two years from now.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-04-26 12:02:18 +08:00
|
|
|
static struct {
|
|
|
|
u16 hotkey_mask_ff:1;
|
2009-12-16 07:51:10 +08:00
|
|
|
u16 volume_ctrl_forbidden:1;
|
ACPI: thinkpad-acpi: warn once about weird hotkey masks
thinkpad-acpi knows for a while now how to best program the hotkeys by
default, and always enable them by default. Unfortunately, this
information has not filtered down everywhere it needs to, yet. Notably,
old ibm-acpi documentation and most "thinkpad setup guides" will have wrong
information on this area.
Warn the local admin once whenever any of the following patterns are met:
1. Attempts to set hotkey mask to 0xffff (artifact from docs and config
for the old ibm-acpi driver and behaviour). This mask makes no
real-world sense;
2. Attempts to set hotkey mask to 0xffffffff, which means the user is
trying to just have "everything work" without even reading the
documentation, or that we need to get a bug report, because there
is a new thinkpad out there with new exciting hot keys :-)
3. Attempts to set hotkey mask to 0xffffff, which is almost never the
correct way to set up volume and brightness event reporting (and with
the current state-of-the-art, it is known to never be right way to do
it).
The driver will perform any and all requested operations, though,
regardless of any warnings. I hope these warnings can be removed one or
two years from now.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-04-26 12:02:18 +08:00
|
|
|
} tp_warned;
|
|
|
|
|
2008-01-08 23:02:43 +08:00
|
|
|
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 */
|
|
|
|
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
u16 bios_model; /* 1Y = 0x5931, 0 = unknown */
|
2008-01-08 23:02:43 +08:00
|
|
|
u16 ec_model;
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */
|
|
|
|
u16 ec_release;
|
2008-01-08 23:02:43 +08:00
|
|
|
|
2008-04-26 12:02:19 +08:00
|
|
|
char *model_str; /* ThinkPad T43 */
|
|
|
|
char *nummodel_str; /* 9384A9C for a 9384-A9C model */
|
2008-01-08 23:02:43 +08:00
|
|
|
};
|
|
|
|
static struct thinkpad_id_data thinkpad_id;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
static enum {
|
|
|
|
TPACPI_LIFE_INIT = 0,
|
|
|
|
TPACPI_LIFE_RUNNING,
|
|
|
|
TPACPI_LIFE_EXITING,
|
|
|
|
} tpacpi_lifecycle;
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int experimental;
|
|
|
|
static u32 dbg_level;
|
|
|
|
|
2008-04-26 12:02:28 +08:00
|
|
|
static struct workqueue_struct *tpacpi_wq;
|
|
|
|
|
2009-04-14 10:44:11 +08:00
|
|
|
enum led_status_t {
|
|
|
|
TPACPI_LED_OFF = 0,
|
|
|
|
TPACPI_LED_ON,
|
|
|
|
TPACPI_LED_BLINK,
|
|
|
|
};
|
|
|
|
|
2008-04-26 12:02:23 +08:00
|
|
|
/* Special LED class that can defer work */
|
|
|
|
struct tpacpi_led_classdev {
|
|
|
|
struct led_classdev led_classdev;
|
|
|
|
struct work_struct work;
|
2009-04-14 10:44:11 +08:00
|
|
|
enum led_status_t new_state;
|
2008-04-26 12:02:25 +08:00
|
|
|
unsigned int led;
|
2008-04-26 12:02:23 +08:00
|
|
|
};
|
|
|
|
|
2010-05-17 06:45:33 +08:00
|
|
|
/* brightness level capabilities */
|
|
|
|
static unsigned int bright_maxlvl; /* 0 = unknown */
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
static int dbg_wlswemul;
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool tpacpi_wlsw_emulstate;
|
2009-01-11 13:01:00 +08:00
|
|
|
static int dbg_bluetoothemul;
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool tpacpi_bluetooth_emulstate;
|
2009-01-11 13:01:00 +08:00
|
|
|
static int dbg_wwanemul;
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool tpacpi_wwan_emulstate;
|
2009-01-11 13:01:03 +08:00
|
|
|
static int dbg_uwbemul;
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool tpacpi_uwb_emulstate;
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2009-04-04 12:25:45 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Debugging helpers
|
|
|
|
*/
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
#define dbg_printk(a_dbg_level, format, arg...) \
|
|
|
|
do { \
|
|
|
|
if (dbg_level & (a_dbg_level)) \
|
|
|
|
printk(KERN_DEBUG pr_fmt("%s: " format), \
|
|
|
|
__func__, ##arg); \
|
|
|
|
} while (0)
|
2009-04-04 12:25:45 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
|
|
|
|
#define vdbg_printk dbg_printk
|
|
|
|
static const char *str_supported(int is_supported);
|
|
|
|
#else
|
2011-04-05 01:06:25 +08:00
|
|
|
static inline const char *str_supported(int is_supported) { return ""; }
|
|
|
|
#define vdbg_printk(a_dbg_level, format, arg...) \
|
|
|
|
no_printk(format, ##arg)
|
2009-04-04 12:25:45 +08:00
|
|
|
#endif
|
|
|
|
|
2009-04-04 12:25:47 +08:00
|
|
|
static void tpacpi_log_usertask(const char * const what)
|
|
|
|
{
|
2011-04-05 01:06:25 +08:00
|
|
|
printk(KERN_DEBUG pr_fmt("%s: access by process with PID %d\n"),
|
|
|
|
what, task_tgid_vnr(current));
|
2009-04-04 12:25:47 +08:00
|
|
|
}
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
#define tpacpi_disclose_usertask(what, format, arg...) \
|
|
|
|
do { \
|
|
|
|
if (unlikely((dbg_level & TPACPI_DBG_DISCLOSETASK) && \
|
|
|
|
(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))) { \
|
|
|
|
printk(KERN_DEBUG pr_fmt("%s: PID %d: " format), \
|
|
|
|
what, task_tgid_vnr(current), ## arg); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
2009-04-04 12:25:45 +08:00
|
|
|
|
2009-05-31 00:25:06 +08:00
|
|
|
/*
|
|
|
|
* Quirk handling helpers
|
|
|
|
*
|
|
|
|
* ThinkPad IDs and versions seen in the field so far
|
|
|
|
* are two-characters from the set [0-9A-Z], i.e. base 36.
|
|
|
|
*
|
|
|
|
* We use values well outside that range as specials.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define TPACPI_MATCH_ANY 0xffffU
|
|
|
|
#define TPACPI_MATCH_UNKNOWN 0U
|
|
|
|
|
|
|
|
/* TPID('1', 'Y') == 0x5931 */
|
|
|
|
#define TPID(__c1, __c2) (((__c2) << 8) | (__c1))
|
|
|
|
|
|
|
|
#define TPACPI_Q_IBM(__id1, __id2, __quirk) \
|
|
|
|
{ .vendor = PCI_VENDOR_ID_IBM, \
|
|
|
|
.bios = TPID(__id1, __id2), \
|
|
|
|
.ec = TPACPI_MATCH_ANY, \
|
|
|
|
.quirks = (__quirk) }
|
|
|
|
|
|
|
|
#define TPACPI_Q_LNV(__id1, __id2, __quirk) \
|
|
|
|
{ .vendor = PCI_VENDOR_ID_LENOVO, \
|
|
|
|
.bios = TPID(__id1, __id2), \
|
|
|
|
.ec = TPACPI_MATCH_ANY, \
|
|
|
|
.quirks = (__quirk) }
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
|
|
|
|
{ .vendor = PCI_VENDOR_ID_LENOVO, \
|
|
|
|
.bios = TPACPI_MATCH_ANY, \
|
|
|
|
.ec = TPID(__id1, __id2), \
|
|
|
|
.quirks = (__quirk) }
|
|
|
|
|
2009-05-31 00:25:06 +08:00
|
|
|
struct tpacpi_quirk {
|
|
|
|
unsigned int vendor;
|
|
|
|
u16 bios;
|
|
|
|
u16 ec;
|
|
|
|
unsigned long quirks;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tpacpi_check_quirks() - search BIOS/EC version on a list
|
|
|
|
* @qlist: array of &struct tpacpi_quirk
|
|
|
|
* @qlist_size: number of elements in @qlist
|
|
|
|
*
|
|
|
|
* Iterates over a quirks list until one is found that matches the
|
|
|
|
* ThinkPad's vendor, BIOS and EC model.
|
|
|
|
*
|
|
|
|
* Returns 0 if nothing matches, otherwise returns the quirks field of
|
|
|
|
* the matching &struct tpacpi_quirk entry.
|
|
|
|
*
|
|
|
|
* The match criteria is: vendor, ec and bios much match.
|
|
|
|
*/
|
|
|
|
static unsigned long __init tpacpi_check_quirks(
|
|
|
|
const struct tpacpi_quirk *qlist,
|
|
|
|
unsigned int qlist_size)
|
|
|
|
{
|
|
|
|
while (qlist_size) {
|
|
|
|
if ((qlist->vendor == thinkpad_id.vendor ||
|
|
|
|
qlist->vendor == TPACPI_MATCH_ANY) &&
|
|
|
|
(qlist->bios == thinkpad_id.bios_model ||
|
|
|
|
qlist->bios == TPACPI_MATCH_ANY) &&
|
|
|
|
(qlist->ec == thinkpad_id.ec_model ||
|
|
|
|
qlist->ec == TPACPI_MATCH_ANY))
|
|
|
|
return qlist->quirks;
|
|
|
|
|
|
|
|
qlist_size--;
|
|
|
|
qlist++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-17 06:45:23 +08:00
|
|
|
static inline bool __pure __init tpacpi_is_lenovo(void)
|
|
|
|
{
|
|
|
|
return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool __pure __init tpacpi_is_ibm(void)
|
|
|
|
{
|
|
|
|
return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
|
|
|
|
}
|
2009-05-31 00:25:06 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* ACPI Helpers and device model
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* ACPI basic handles
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static acpi_handle root_handle;
|
2010-05-17 06:45:43 +08:00
|
|
|
static acpi_handle ec_handle;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
#define TPACPI_HANDLE(object, parent, paths...) \
|
2005-04-17 06:20:36 +08:00
|
|
|
static acpi_handle object##_handle; \
|
2010-05-17 06:45:54 +08:00
|
|
|
static const acpi_handle *object##_parent __initdata = \
|
|
|
|
&parent##_handle; \
|
|
|
|
static char *object##_paths[] __initdata = { paths }
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */
|
|
|
|
TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
TPACPI_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, */
|
|
|
|
/* T4x, X31, X40 */
|
2005-08-17 12:00:00 +08:00
|
|
|
"\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */
|
|
|
|
"\\CMS", /* R40, R40e */
|
2007-03-24 04:33:57 +08:00
|
|
|
); /* all others */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
|
2005-08-17 12:00:00 +08:00
|
|
|
"^HKEY", /* R30, R31 */
|
|
|
|
"HKEY", /* all others */
|
2007-03-24 04:33:57 +08:00
|
|
|
); /* 570 */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* ACPI helpers
|
2006-11-24 21:47:11 +08:00
|
|
|
*/
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int acpi_evalf(acpi_handle handle,
|
|
|
|
void *res, char *method, char *fmt, ...)
|
|
|
|
{
|
|
|
|
char *fmt0 = fmt;
|
2005-08-17 12:00:00 +08:00
|
|
|
struct acpi_object_list params;
|
2008-01-08 23:02:48 +08:00
|
|
|
union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS];
|
2005-08-17 12:00:00 +08:00
|
|
|
struct acpi_buffer result, *resultp;
|
|
|
|
union acpi_object out_obj;
|
|
|
|
acpi_status status;
|
|
|
|
va_list ap;
|
|
|
|
char res_type;
|
|
|
|
int success;
|
|
|
|
int quiet;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!*fmt) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_evalf() called with empty format\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*fmt == 'q') {
|
|
|
|
quiet = 1;
|
|
|
|
fmt++;
|
|
|
|
} else
|
|
|
|
quiet = 0;
|
|
|
|
|
|
|
|
res_type = *(fmt++);
|
|
|
|
|
|
|
|
params.count = 0;
|
|
|
|
params.pointer = &in_objs[0];
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
while (*fmt) {
|
|
|
|
char c = *(fmt++);
|
|
|
|
switch (c) {
|
|
|
|
case 'd': /* int */
|
|
|
|
in_objs[params.count].integer.value = va_arg(ap, int);
|
|
|
|
in_objs[params.count++].type = ACPI_TYPE_INTEGER;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
/* add more types as needed */
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_evalf() called "
|
2005-04-17 06:20:36 +08:00
|
|
|
"with invalid format character '%c'\n", c);
|
2010-12-25 02:56:28 +08:00
|
|
|
va_end(ap);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (res_type != 'v') {
|
|
|
|
result.length = sizeof(out_obj);
|
|
|
|
result.pointer = &out_obj;
|
|
|
|
resultp = &result;
|
|
|
|
} else
|
|
|
|
resultp = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
status = acpi_evaluate_object(handle, method, ¶ms, resultp);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
switch (res_type) {
|
2005-08-17 12:00:00 +08:00
|
|
|
case 'd': /* int */
|
2010-05-17 06:45:45 +08:00
|
|
|
success = (status == AE_OK &&
|
|
|
|
out_obj.type == ACPI_TYPE_INTEGER);
|
|
|
|
if (success && res)
|
2005-04-17 06:20:36 +08:00
|
|
|
*(int *)res = out_obj.integer.value;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
case 'v': /* void */
|
2005-04-17 06:20:36 +08:00
|
|
|
success = status == AE_OK;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
/* add more types as needed */
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_evalf() called "
|
2005-04-17 06:20:36 +08:00
|
|
|
"with invalid format character '%c'\n", res_type);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
if (!success && !quiet)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
|
2010-05-17 06:45:45 +08:00
|
|
|
method, fmt0, acpi_format_exception(status));
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
static int acpi_ec_read(int i, u8 *p)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int v;
|
|
|
|
|
|
|
|
if (ecrd_handle) {
|
|
|
|
if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
|
|
|
|
return 0;
|
|
|
|
*p = v;
|
|
|
|
} else {
|
|
|
|
if (ec_read(i, p) < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int acpi_ec_write(int i, u8 v)
|
|
|
|
{
|
|
|
|
if (ecwr_handle) {
|
|
|
|
if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (ec_write(i, v) < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
static int issue_thinkpad_cmos_command(int cmos_cmd)
|
|
|
|
{
|
|
|
|
if (!cmos_handle)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* ACPI device model
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
#define TPACPI_ACPIHANDLE_INIT(object) \
|
|
|
|
drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
|
2010-05-17 06:45:54 +08:00
|
|
|
object##_paths, ARRAY_SIZE(object##_paths))
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2010-05-17 06:45:54 +08:00
|
|
|
static void __init drv_acpi_handle_init(const char *name,
|
|
|
|
acpi_handle *handle, const acpi_handle parent,
|
|
|
|
char **paths, const int num_paths)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
acpi_status status;
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
|
|
|
|
name);
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
for (i = 0; i < num_paths; i++) {
|
|
|
|
status = acpi_get_handle(parent, paths[i], handle);
|
|
|
|
if (ACPI_SUCCESS(status)) {
|
2007-04-28 09:00:14 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"Found ACPI handle %s for %s\n",
|
2010-05-17 06:45:54 +08:00
|
|
|
paths[i], name);
|
2007-03-24 04:33:57 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
|
|
|
|
name);
|
2007-03-24 04:33:57 +08:00
|
|
|
*handle = NULL;
|
|
|
|
}
|
|
|
|
|
2010-05-17 06:45:43 +08:00
|
|
|
static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
|
|
|
|
u32 level, void *context, void **return_value)
|
|
|
|
{
|
|
|
|
*(acpi_handle *)return_value = handle;
|
|
|
|
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __init tpacpi_acpi_handle_locate(const char *name,
|
|
|
|
const char *hid,
|
|
|
|
acpi_handle *handle)
|
|
|
|
{
|
|
|
|
acpi_status status;
|
|
|
|
acpi_handle device_found;
|
|
|
|
|
|
|
|
BUG_ON(!name || !hid || !handle);
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"trying to locate ACPI handle for %s, using HID %s\n",
|
|
|
|
name, hid);
|
|
|
|
|
|
|
|
memset(&device_found, 0, sizeof(device_found));
|
|
|
|
status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
|
|
|
|
(void *)name, &device_found);
|
|
|
|
|
|
|
|
*handle = NULL;
|
|
|
|
|
|
|
|
if (ACPI_SUCCESS(status)) {
|
|
|
|
*handle = device_found;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"Found ACPI handle for %s\n", name);
|
|
|
|
} else {
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"Could not locate an ACPI handle for %s: %s\n",
|
|
|
|
name, acpi_format_exception(status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm = data;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (!ibm || !ibm->acpi || !ibm->acpi->notify)
|
2007-03-24 04:33:57 +08:00
|
|
|
return;
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->acpi->notify(ibm, event);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
acpi_status status;
|
2007-04-28 09:00:14 +08:00
|
|
|
int rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
|
|
|
|
if (!*ibm->acpi->handle)
|
2007-03-24 04:33:57 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
2007-04-21 22:08:32 +08:00
|
|
|
"setting up ACPI notify for %s\n", ibm->name);
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc);
|
2007-03-24 04:33:57 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2008-09-23 05:37:34 +08:00
|
|
|
ibm->acpi->device->driver_data = ibm;
|
2007-04-21 22:08:37 +08:00
|
|
|
sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPI_EVENT_PREFIX,
|
2007-03-29 12:58:43 +08:00
|
|
|
ibm->name);
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
status = acpi_install_notify_handler(*ibm->acpi->handle,
|
|
|
|
ibm->acpi->type, dispatch_acpi_notify, ibm);
|
2007-03-24 04:33:57 +08:00
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
|
if (status == AE_ALREADY_EXISTS) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("another device driver is already "
|
|
|
|
"handling %s events\n", ibm->name);
|
2007-03-24 04:33:57 +08:00
|
|
|
} else {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_install_notify_handler(%s) failed: %s\n",
|
2010-05-17 06:45:48 +08:00
|
|
|
ibm->name, acpi_format_exception(status));
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->flags.acpi_notify_installed = 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int __init tpacpi_device_add(struct acpi_device *device)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:25 +08:00
|
|
|
static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2007-04-28 09:00:14 +08:00
|
|
|
int rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"registering %s as an ACPI driver\n", ibm->name);
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
|
|
|
|
ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
|
|
|
|
if (!ibm->acpi->driver) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to allocate memory for ibm->acpi->driver\n");
|
2007-04-21 22:08:31 +08:00
|
|
|
return -ENOMEM;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name);
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->acpi->driver->ids = ibm->acpi->hid;
|
2007-07-23 20:44:41 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->acpi->driver->ops.add = &tpacpi_device_add;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
rc = acpi_bus_register_driver(ibm->acpi->driver);
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("acpi_bus_register_driver(%s) failed: %d\n",
|
2007-07-23 20:44:41 +08:00
|
|
|
ibm->name, rc);
|
2007-04-21 22:08:37 +08:00
|
|
|
kfree(ibm->acpi->driver);
|
|
|
|
ibm->acpi->driver = NULL;
|
2007-04-28 09:00:14 +08:00
|
|
|
} else if (!rc)
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->flags.acpi_driver_registered = 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
return rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Procfs Helpers
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int dispatch_proc_show(struct seq_file *m, void *v)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2009-12-16 07:51:12 +08:00
|
|
|
struct ibm_struct *ibm = m->private;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
if (!ibm || !ibm->read)
|
|
|
|
return -EINVAL;
|
2009-12-16 07:51:12 +08:00
|
|
|
return ibm->read(m);
|
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int dispatch_proc_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
return single_open(file, dispatch_proc_show, PDE(inode)->data);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static ssize_t dispatch_proc_write(struct file *file,
|
2008-01-08 23:02:49 +08:00
|
|
|
const char __user *userbuf,
|
2009-12-16 07:51:12 +08:00
|
|
|
size_t count, loff_t *pos)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2009-12-16 07:51:12 +08:00
|
|
|
struct ibm_struct *ibm = PDE(file->f_path.dentry->d_inode)->data;
|
2007-03-24 04:33:57 +08:00
|
|
|
char *kernbuf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!ibm || !ibm->write)
|
|
|
|
return -EINVAL;
|
2009-08-01 23:04:19 +08:00
|
|
|
if (count > PAGE_SIZE - 2)
|
|
|
|
return -EINVAL;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
kernbuf = kmalloc(count + 2, GFP_KERNEL);
|
|
|
|
if (!kernbuf)
|
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
if (copy_from_user(kernbuf, userbuf, count)) {
|
|
|
|
kfree(kernbuf);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
kernbuf[count] = 0;
|
|
|
|
strcat(kernbuf, ",");
|
|
|
|
ret = ibm->write(kernbuf);
|
|
|
|
if (ret == 0)
|
|
|
|
ret = count;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
kfree(kernbuf);
|
|
|
|
|
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static const struct file_operations dispatch_proc_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = dispatch_proc_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = single_release,
|
|
|
|
.write = dispatch_proc_write,
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static char *next_cmd(char **cmds)
|
|
|
|
{
|
|
|
|
char *start = *cmds;
|
|
|
|
char *end;
|
|
|
|
|
|
|
|
while ((end = strchr(start, ',')) && end == start)
|
|
|
|
start = end + 1;
|
|
|
|
|
|
|
|
if (!end)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*end = 0;
|
|
|
|
*cmds = end + 1;
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
2007-07-19 10:45:34 +08:00
|
|
|
* Device model: input, hwmon and platform
|
2007-04-24 22:48:12 +08:00
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct platform_device *tpacpi_pdev;
|
2007-09-25 17:38:03 +08:00
|
|
|
static struct platform_device *tpacpi_sensors_pdev;
|
2007-08-21 04:46:20 +08:00
|
|
|
static struct device *tpacpi_hwmon;
|
2007-07-19 10:45:34 +08:00
|
|
|
static struct input_dev *tpacpi_inputdev;
|
2007-09-23 22:39:01 +08:00
|
|
|
static struct mutex tpacpi_inputdev_send_mutex;
|
2008-01-08 23:02:45 +08:00
|
|
|
static LIST_HEAD(tpacpi_all_drivers);
|
2007-07-19 10:45:37 +08:00
|
|
|
|
2012-08-10 05:00:13 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2012-06-28 05:19:01 +08:00
|
|
|
static int tpacpi_suspend_handler(struct device *dev)
|
2008-01-08 23:02:50 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm, *itmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(ibm, itmp,
|
|
|
|
&tpacpi_all_drivers,
|
|
|
|
all_drivers) {
|
|
|
|
if (ibm->suspend)
|
2012-06-28 05:18:44 +08:00
|
|
|
(ibm->suspend)();
|
2008-01-08 23:02:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-06-28 05:19:01 +08:00
|
|
|
static int tpacpi_resume_handler(struct device *dev)
|
2007-07-19 10:45:37 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm, *itmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(ibm, itmp,
|
|
|
|
&tpacpi_all_drivers,
|
|
|
|
all_drivers) {
|
|
|
|
if (ibm->resume)
|
|
|
|
(ibm->resume)();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2012-08-10 05:00:13 +08:00
|
|
|
#endif
|
2007-07-19 10:45:37 +08:00
|
|
|
|
2012-06-28 05:19:01 +08:00
|
|
|
static SIMPLE_DEV_PM_OPS(tpacpi_pm,
|
|
|
|
tpacpi_suspend_handler, tpacpi_resume_handler);
|
|
|
|
|
2009-01-11 13:01:02 +08:00
|
|
|
static void tpacpi_shutdown_handler(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct ibm_struct *ibm, *itmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(ibm, itmp,
|
|
|
|
&tpacpi_all_drivers,
|
|
|
|
all_drivers) {
|
|
|
|
if (ibm->shutdown)
|
|
|
|
(ibm->shutdown)();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
static struct platform_driver tpacpi_pdriver = {
|
|
|
|
.driver = {
|
2008-01-08 23:02:48 +08:00
|
|
|
.name = TPACPI_DRVR_NAME,
|
2007-04-24 22:48:12 +08:00
|
|
|
.owner = THIS_MODULE,
|
2012-06-28 05:19:01 +08:00
|
|
|
.pm = &tpacpi_pm,
|
2007-04-24 22:48:12 +08:00
|
|
|
},
|
2009-01-11 13:01:02 +08:00
|
|
|
.shutdown = tpacpi_shutdown_handler,
|
2007-04-24 22:48:12 +08:00
|
|
|
};
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
static struct platform_driver tpacpi_hwmon_pdriver = {
|
|
|
|
.driver = {
|
2008-01-08 23:02:48 +08:00
|
|
|
.name = TPACPI_HWMON_DRVR_NAME,
|
2007-09-25 17:38:03 +08:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
};
|
2007-04-24 22:48:12 +08:00
|
|
|
|
2007-04-24 22:48:13 +08:00
|
|
|
/*************************************************************************
|
2008-01-08 23:02:45 +08:00
|
|
|
* sysfs support helpers
|
2007-04-24 22:48:13 +08:00
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
struct attribute_set {
|
|
|
|
unsigned int members, max_members;
|
|
|
|
struct attribute_group group;
|
2007-04-24 22:48:13 +08:00
|
|
|
};
|
|
|
|
|
2007-04-24 22:48:14 +08:00
|
|
|
struct attribute_set_obj {
|
|
|
|
struct attribute_set s;
|
|
|
|
struct attribute *a;
|
|
|
|
} __attribute__((packed));
|
|
|
|
|
|
|
|
static struct attribute_set *create_attr_set(unsigned int max_members,
|
2008-01-08 23:02:49 +08:00
|
|
|
const char *name)
|
2007-04-24 22:48:14 +08:00
|
|
|
{
|
|
|
|
struct attribute_set_obj *sobj;
|
|
|
|
|
|
|
|
if (max_members == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Allocates space for implicit NULL at the end too */
|
|
|
|
sobj = kzalloc(sizeof(struct attribute_set_obj) +
|
|
|
|
max_members * sizeof(struct attribute *),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!sobj)
|
|
|
|
return NULL;
|
|
|
|
sobj->s.max_members = max_members;
|
|
|
|
sobj->s.group.attrs = &sobj->a;
|
|
|
|
sobj->s.group.name = name;
|
|
|
|
|
|
|
|
return &sobj->s;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
#define destroy_attr_set(_set) \
|
|
|
|
kfree(_set);
|
|
|
|
|
2007-04-24 22:48:14 +08:00
|
|
|
/* not multi-threaded safe, use it in a single thread per set */
|
2008-01-08 23:02:49 +08:00
|
|
|
static int add_to_attr_set(struct attribute_set *s, struct attribute *attr)
|
2007-04-24 22:48:14 +08:00
|
|
|
{
|
|
|
|
if (!s || !attr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (s->members >= s->max_members)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
s->group.attrs[s->members] = attr;
|
|
|
|
s->members++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
static int add_many_to_attr_set(struct attribute_set *s,
|
2007-04-24 22:48:14 +08:00
|
|
|
struct attribute **attr,
|
|
|
|
unsigned int count)
|
|
|
|
{
|
|
|
|
int i, res;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
res = add_to_attr_set(s, attr[i]);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
static void delete_attr_set(struct attribute_set *s, struct kobject *kobj)
|
2007-04-24 22:48:14 +08:00
|
|
|
{
|
|
|
|
sysfs_remove_group(kobj, &s->group);
|
|
|
|
destroy_attr_set(s);
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
#define register_attr_set_with_sysfs(_attr_set, _kobj) \
|
|
|
|
sysfs_create_group(_kobj, &_attr_set->group)
|
|
|
|
|
2007-04-24 22:48:14 +08:00
|
|
|
static int parse_strtoul(const char *buf,
|
|
|
|
unsigned long max, unsigned long *value)
|
|
|
|
{
|
|
|
|
char *endp;
|
|
|
|
|
2009-12-15 10:01:06 +08:00
|
|
|
*value = simple_strtoul(skip_spaces(buf), &endp, 0);
|
|
|
|
endp = skip_spaces(endp);
|
2007-04-24 22:48:14 +08:00
|
|
|
if (*endp || *value > max)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-10-19 01:23:55 +08:00
|
|
|
static void tpacpi_disable_brightness_delay(void)
|
|
|
|
{
|
|
|
|
if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0))
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("ACPI backlight control delay disabled\n");
|
2008-10-19 01:23:55 +08:00
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static void printk_deprecated_attribute(const char * const what,
|
|
|
|
const char * const details)
|
|
|
|
{
|
|
|
|
tpacpi_log_usertask("deprecated sysfs attribute");
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_warn("WARNING: sysfs attribute %s is deprecated and "
|
2009-06-02 19:01:37 +08:00
|
|
|
"will be removed. %s\n",
|
|
|
|
what, details);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* rfkill and radio control support helpers
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ThinkPad-ACPI firmware handling model:
|
|
|
|
*
|
|
|
|
* WLSW (master wireless switch) is event-driven, and is common to all
|
|
|
|
* firmware-controlled radios. It cannot be controlled, just monitored,
|
|
|
|
* as expected. It overrides all radio state in firmware
|
|
|
|
*
|
|
|
|
* The kernel, a masked-off hotkey, and WLSW can change the radio state
|
|
|
|
* (TODO: verify how WLSW interacts with the returned radio state).
|
|
|
|
*
|
|
|
|
* The only time there are shadow radio state changes, is when
|
|
|
|
* masked-off hotkeys are used.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Internal driver API for radio state:
|
|
|
|
*
|
|
|
|
* int: < 0 = error, otherwise enum tpacpi_rfkill_state
|
|
|
|
* bool: true means radio blocked (off)
|
|
|
|
*/
|
|
|
|
enum tpacpi_rfkill_state {
|
|
|
|
TPACPI_RFK_RADIO_OFF = 0,
|
|
|
|
TPACPI_RFK_RADIO_ON
|
|
|
|
};
|
|
|
|
|
|
|
|
/* rfkill switches */
|
|
|
|
enum tpacpi_rfk_id {
|
|
|
|
TPACPI_RFK_BLUETOOTH_SW_ID = 0,
|
|
|
|
TPACPI_RFK_WWAN_SW_ID,
|
|
|
|
TPACPI_RFK_UWB_SW_ID,
|
|
|
|
TPACPI_RFK_SW_MAX
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *tpacpi_rfkill_names[] = {
|
|
|
|
[TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth",
|
|
|
|
[TPACPI_RFK_WWAN_SW_ID] = "wwan",
|
|
|
|
[TPACPI_RFK_UWB_SW_ID] = "uwb",
|
|
|
|
[TPACPI_RFK_SW_MAX] = NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
/* ThinkPad-ACPI rfkill subdriver */
|
|
|
|
struct tpacpi_rfk {
|
|
|
|
struct rfkill *rfkill;
|
|
|
|
enum tpacpi_rfk_id id;
|
|
|
|
const struct tpacpi_rfk_ops *ops;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct tpacpi_rfk_ops {
|
|
|
|
/* firmware interface */
|
|
|
|
int (*get_status)(void);
|
|
|
|
int (*set_status)(const enum tpacpi_rfkill_state);
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX];
|
|
|
|
|
|
|
|
/* Query FW and update rfkill sw state for a given rfkill switch */
|
|
|
|
static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!tp_rfk)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
status = (tp_rfk->ops->get_status)();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
rfkill_set_sw_state(tp_rfk->rfkill,
|
|
|
|
(status == TPACPI_RFK_RADIO_OFF));
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Query FW and update rfkill sw state for all rfkill switches */
|
|
|
|
static void tpacpi_rfk_update_swstate_all(void)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < TPACPI_RFK_SW_MAX; i++)
|
|
|
|
tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sync the HW-blocking state of all rfkill switches,
|
|
|
|
* do notice it causes the rfkill core to schedule uevents
|
|
|
|
*/
|
|
|
|
static void tpacpi_rfk_update_hwblock_state(bool blocked)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
struct tpacpi_rfk *tp_rfk;
|
|
|
|
|
|
|
|
for (i = 0; i < TPACPI_RFK_SW_MAX; i++) {
|
|
|
|
tp_rfk = tpacpi_rfkill_switches[i];
|
|
|
|
if (tp_rfk) {
|
|
|
|
if (rfkill_set_hw_state(tp_rfk->rfkill,
|
|
|
|
blocked)) {
|
|
|
|
/* ignore -- we track sw block */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Call to get the WLSW state from the firmware */
|
|
|
|
static int hotkey_get_wlsw(void);
|
|
|
|
|
|
|
|
/* Call to query WLSW state and update all rfkill switches */
|
|
|
|
static bool tpacpi_rfk_check_hwblock_state(void)
|
|
|
|
{
|
|
|
|
int res = hotkey_get_wlsw();
|
|
|
|
int hw_blocked;
|
|
|
|
|
|
|
|
/* When unknown or unsupported, we have to assume it is unblocked */
|
|
|
|
if (res < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
hw_blocked = (res == TPACPI_RFK_RADIO_OFF);
|
|
|
|
tpacpi_rfk_update_hwblock_state(hw_blocked);
|
|
|
|
|
|
|
|
return hw_blocked;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tpacpi_rfk_hook_set_block(void *data, bool blocked)
|
|
|
|
{
|
|
|
|
struct tpacpi_rfk *tp_rfk = data;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_RFKILL,
|
|
|
|
"request to change radio state to %s\n",
|
|
|
|
blocked ? "blocked" : "unblocked");
|
|
|
|
|
|
|
|
/* try to set radio state */
|
|
|
|
res = (tp_rfk->ops->set_status)(blocked ?
|
|
|
|
TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON);
|
|
|
|
|
|
|
|
/* and update the rfkill core with whatever the FW really did */
|
|
|
|
tpacpi_rfk_update_swstate(tp_rfk);
|
|
|
|
|
|
|
|
return (res < 0) ? res : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct rfkill_ops tpacpi_rfk_rfkill_ops = {
|
|
|
|
.set_block = tpacpi_rfk_hook_set_block,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
|
|
|
|
const struct tpacpi_rfk_ops *tp_rfkops,
|
2008-07-21 20:15:51 +08:00
|
|
|
const enum rfkill_type rfktype,
|
|
|
|
const char *name,
|
2009-06-02 19:01:37 +08:00
|
|
|
const bool set_default)
|
2008-07-21 20:15:51 +08:00
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
struct tpacpi_rfk *atp_rfk;
|
2008-07-21 20:15:51 +08:00
|
|
|
int res;
|
2009-06-16 22:39:51 +08:00
|
|
|
bool sw_state = false;
|
2009-12-16 07:51:07 +08:00
|
|
|
bool hw_state;
|
2009-06-16 22:39:51 +08:00
|
|
|
int sw_status;
|
2009-01-11 13:01:02 +08:00
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
|
|
|
|
|
|
|
|
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
|
|
|
|
if (atp_rfk)
|
|
|
|
atp_rfk->rfkill = rfkill_alloc(name,
|
|
|
|
&tpacpi_pdev->dev,
|
|
|
|
rfktype,
|
|
|
|
&tpacpi_rfk_rfkill_ops,
|
|
|
|
atp_rfk);
|
|
|
|
if (!atp_rfk || !atp_rfk->rfkill) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to allocate memory for rfkill class\n");
|
2009-06-02 19:01:37 +08:00
|
|
|
kfree(atp_rfk);
|
2008-07-21 20:15:51 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
atp_rfk->id = id;
|
|
|
|
atp_rfk->ops = tp_rfkops;
|
2008-07-21 20:15:51 +08:00
|
|
|
|
2009-06-16 22:39:51 +08:00
|
|
|
sw_status = (tp_rfkops->get_status)();
|
|
|
|
if (sw_status < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to read initial state for %s, error %d\n",
|
|
|
|
name, sw_status);
|
2009-06-08 20:27:27 +08:00
|
|
|
} else {
|
2009-06-16 22:39:51 +08:00
|
|
|
sw_state = (sw_status == TPACPI_RFK_RADIO_OFF);
|
2009-06-08 20:27:27 +08:00
|
|
|
if (set_default) {
|
|
|
|
/* try to keep the initial state, since we ask the
|
|
|
|
* firmware to preserve it across S5 in NVRAM */
|
2009-06-16 22:39:51 +08:00
|
|
|
rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
|
2009-06-08 20:27:27 +08:00
|
|
|
}
|
|
|
|
}
|
2009-12-16 07:51:07 +08:00
|
|
|
hw_state = tpacpi_rfk_check_hwblock_state();
|
|
|
|
rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
res = rfkill_register(atp_rfk->rfkill);
|
2008-07-21 20:15:51 +08:00
|
|
|
if (res < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to register %s rfkill switch: %d\n", name, res);
|
2009-06-02 19:01:37 +08:00
|
|
|
rfkill_destroy(atp_rfk->rfkill);
|
|
|
|
kfree(atp_rfk);
|
2008-07-21 20:15:51 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_rfkill_switches[id] = atp_rfk;
|
2009-12-16 07:51:07 +08:00
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("rfkill switch %s: radio is %sblocked\n",
|
2009-12-16 07:51:07 +08:00
|
|
|
name, (sw_state || hw_state) ? "" : "un");
|
2008-07-21 20:15:51 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id)
|
2009-04-04 12:25:47 +08:00
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
struct tpacpi_rfk *tp_rfk;
|
|
|
|
|
|
|
|
BUG_ON(id >= TPACPI_RFK_SW_MAX);
|
|
|
|
|
|
|
|
tp_rfk = tpacpi_rfkill_switches[id];
|
|
|
|
if (tp_rfk) {
|
|
|
|
rfkill_unregister(tp_rfk->rfkill);
|
2009-09-14 18:43:52 +08:00
|
|
|
rfkill_destroy(tp_rfk->rfkill);
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_rfkill_switches[id] = NULL;
|
|
|
|
kfree(tp_rfk);
|
|
|
|
}
|
2009-04-04 12:25:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void printk_deprecated_rfkill_attribute(const char * const what)
|
|
|
|
{
|
|
|
|
printk_deprecated_attribute(what,
|
|
|
|
"Please switch to generic rfkill before year 2010");
|
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
/* sysfs <radio> enable ------------------------------------------------ */
|
|
|
|
static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
printk_deprecated_rfkill_attribute(attr->attr.name);
|
|
|
|
|
|
|
|
/* This is in the ABI... */
|
|
|
|
if (tpacpi_rfk_check_hwblock_state()) {
|
|
|
|
status = TPACPI_RFK_RADIO_OFF;
|
|
|
|
} else {
|
|
|
|
status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
|
|
(status == TPACPI_RFK_RADIO_ON) ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
printk_deprecated_rfkill_attribute(attr->attr.name);
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t);
|
|
|
|
|
|
|
|
/* This is in the ABI... */
|
|
|
|
if (tpacpi_rfk_check_hwblock_state() && !!t)
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ?
|
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF);
|
|
|
|
tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
|
|
|
|
|
|
|
|
return (res < 0) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* procfs -------------------------------------------------------------- */
|
2009-12-16 07:51:12 +08:00
|
|
|
static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
|
2009-06-02 19:01:37 +08:00
|
|
|
{
|
|
|
|
if (id >= TPACPI_RFK_SW_MAX)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
2009-06-02 19:01:37 +08:00
|
|
|
else {
|
|
|
|
int status;
|
|
|
|
|
|
|
|
/* This is in the ABI... */
|
|
|
|
if (tpacpi_rfk_check_hwblock_state()) {
|
|
|
|
status = TPACPI_RFK_RADIO_OFF;
|
|
|
|
} else {
|
|
|
|
status = tpacpi_rfk_update_swstate(
|
|
|
|
tpacpi_rfkill_switches[id]);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\t%s\n",
|
2009-06-02 19:01:37 +08:00
|
|
|
(status == TPACPI_RFK_RADIO_ON) ?
|
|
|
|
"enabled" : "disabled");
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tenable, disable\n");
|
2009-06-02 19:01:37 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2009-06-02 19:01:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int status = -1;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
if (id >= TPACPI_RFK_SW_MAX)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "enable") == 0)
|
|
|
|
status = TPACPI_RFK_RADIO_ON;
|
|
|
|
else if (strlencmp(cmd, "disable") == 0)
|
|
|
|
status = TPACPI_RFK_RADIO_OFF;
|
|
|
|
else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status != -1) {
|
|
|
|
tpacpi_disclose_usertask("procfs", "attempt to %s %s\n",
|
|
|
|
(status == TPACPI_RFK_RADIO_ON) ?
|
|
|
|
"enable" : "disable",
|
|
|
|
tpacpi_rfkill_names[id]);
|
|
|
|
res = (tpacpi_rfkill_switches[id]->ops->set_status)(status);
|
|
|
|
tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
2008-01-08 23:02:45 +08:00
|
|
|
* thinkpad-acpi driver attributes
|
2007-03-24 04:33:57 +08:00
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* interface_version --------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_interface_version_show(
|
|
|
|
struct device_driver *drv,
|
|
|
|
char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
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)
|
|
|
|
{
|
2008-01-08 23:02:49 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s v%s\n",
|
|
|
|
TPACPI_DESC, TPACPI_VERSION);
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(version, S_IRUGO,
|
|
|
|
tpacpi_driver_version_show, NULL);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
|
|
|
|
/* wlsw_emulstate ------------------------------------------------------ */
|
|
|
|
static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wlsw_emulstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
if (tpacpi_wlsw_emulstate != !!t) {
|
2009-01-11 13:01:00 +08:00
|
|
|
tpacpi_wlsw_emulstate = !!t;
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_rfk_update_hwblock_state(!t); /* negative logic */
|
|
|
|
}
|
2009-01-11 13:01:00 +08:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(wlsw_emulstate, S_IWUSR | S_IRUGO,
|
|
|
|
tpacpi_driver_wlsw_emulstate_show,
|
|
|
|
tpacpi_driver_wlsw_emulstate_store);
|
|
|
|
|
|
|
|
/* bluetooth_emulstate ------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_bluetooth_emulstate_show(
|
|
|
|
struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_bluetooth_emulstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_driver_bluetooth_emulstate_store(
|
|
|
|
struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tpacpi_bluetooth_emulstate = !!t;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(bluetooth_emulstate, S_IWUSR | S_IRUGO,
|
|
|
|
tpacpi_driver_bluetooth_emulstate_show,
|
|
|
|
tpacpi_driver_bluetooth_emulstate_store);
|
|
|
|
|
|
|
|
/* wwan_emulstate ------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_wwan_emulstate_show(
|
|
|
|
struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wwan_emulstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_driver_wwan_emulstate_store(
|
|
|
|
struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tpacpi_wwan_emulstate = !!t;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
|
|
|
|
tpacpi_driver_wwan_emulstate_show,
|
|
|
|
tpacpi_driver_wwan_emulstate_store);
|
|
|
|
|
2009-01-11 13:01:03 +08:00
|
|
|
/* uwb_emulstate ------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_uwb_emulstate_show(
|
|
|
|
struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_uwb_emulstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_driver_uwb_emulstate_store(
|
|
|
|
struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tpacpi_uwb_emulstate = !!t;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(uwb_emulstate, S_IWUSR | S_IRUGO,
|
|
|
|
tpacpi_driver_uwb_emulstate_show,
|
|
|
|
tpacpi_driver_uwb_emulstate_store);
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
static struct driver_attribute *tpacpi_driver_attributes[] = {
|
2008-01-08 23:02:45 +08:00
|
|
|
&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++;
|
|
|
|
}
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (!res && dbg_wlswemul)
|
|
|
|
res = driver_create_file(drv, &driver_attr_wlsw_emulstate);
|
|
|
|
if (!res && dbg_bluetoothemul)
|
|
|
|
res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
|
|
|
|
if (!res && dbg_wwanemul)
|
|
|
|
res = driver_create_file(drv, &driver_attr_wwan_emulstate);
|
2009-01-11 13:01:03 +08:00
|
|
|
if (!res && dbg_uwbemul)
|
|
|
|
res = driver_create_file(drv, &driver_attr_uwb_emulstate);
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tpacpi_remove_driver_attributes(struct device_driver *drv)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
|
2008-01-08 23:02:45 +08:00
|
|
|
driver_remove_file(drv, tpacpi_driver_attributes[i]);
|
2009-01-11 13:01:00 +08:00
|
|
|
|
|
|
|
#ifdef THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
driver_remove_file(drv, &driver_attr_wlsw_emulstate);
|
|
|
|
driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
|
|
|
|
driver_remove_file(drv, &driver_attr_wwan_emulstate);
|
2009-01-11 13:01:03 +08:00
|
|
|
driver_remove_file(drv, &driver_attr_uwb_emulstate);
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
2009-09-13 02:22:12 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Firmware Data
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Table of recommended minimum BIOS versions
|
|
|
|
*
|
|
|
|
* Reasons for listing:
|
2010-01-21 00:02:24 +08:00
|
|
|
* 1. Stable BIOS, listed because the unknown amount of
|
2009-09-13 02:22:12 +08:00
|
|
|
* bugs and bad ACPI behaviour on older versions
|
|
|
|
*
|
|
|
|
* 2. BIOS or EC fw with known bugs that trigger on Linux
|
|
|
|
*
|
|
|
|
* 3. BIOS with known reduced functionality in older versions
|
|
|
|
*
|
|
|
|
* We recommend the latest BIOS and EC version.
|
|
|
|
* We only support the latest BIOS and EC fw version as a rule.
|
|
|
|
*
|
|
|
|
* Sources: IBM ThinkPad Public Web Documents (update changelogs),
|
|
|
|
* Information from users in ThinkWiki
|
2009-09-13 02:22:13 +08:00
|
|
|
*
|
|
|
|
* WARNING: we use this table also to detect that the machine is
|
|
|
|
* a ThinkPad in some cases, so don't remove entries lightly.
|
2009-09-13 02:22:12 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define TPV_Q(__v, __id1, __id2, __bv1, __bv2) \
|
|
|
|
{ .vendor = (__v), \
|
|
|
|
.bios = TPID(__id1, __id2), \
|
|
|
|
.ec = TPACPI_MATCH_ANY, \
|
|
|
|
.quirks = TPACPI_MATCH_ANY << 16 \
|
|
|
|
| (__bv1) << 8 | (__bv2) }
|
|
|
|
|
|
|
|
#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2, \
|
2009-11-18 06:07:22 +08:00
|
|
|
__eid, __ev1, __ev2) \
|
2009-09-13 02:22:12 +08:00
|
|
|
{ .vendor = (__v), \
|
|
|
|
.bios = TPID(__bid1, __bid2), \
|
2009-11-18 06:07:22 +08:00
|
|
|
.ec = __eid, \
|
2009-09-13 02:22:12 +08:00
|
|
|
.quirks = (__ev1) << 24 | (__ev2) << 16 \
|
|
|
|
| (__bv1) << 8 | (__bv2) }
|
|
|
|
|
|
|
|
#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
|
|
|
|
TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)
|
|
|
|
|
2009-11-18 06:07:22 +08:00
|
|
|
/* Outdated IBM BIOSes often lack the EC id string */
|
2009-09-13 02:22:12 +08:00
|
|
|
#define TPV_QI1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, \
|
2009-11-18 06:07:22 +08:00
|
|
|
__bv1, __bv2, TPID(__id1, __id2), \
|
|
|
|
__ev1, __ev2), \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, \
|
|
|
|
__bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
|
|
|
|
__ev1, __ev2)
|
2009-09-13 02:22:12 +08:00
|
|
|
|
2009-11-18 06:07:22 +08:00
|
|
|
/* Outdated IBM BIOSes often lack the EC id string */
|
2009-09-13 02:22:12 +08:00
|
|
|
#define TPV_QI2(__bid1, __bid2, __bv1, __bv2, \
|
|
|
|
__eid1, __eid2, __ev1, __ev2) \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, \
|
2009-11-18 06:07:22 +08:00
|
|
|
__bv1, __bv2, TPID(__eid1, __eid2), \
|
|
|
|
__ev1, __ev2), \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, \
|
|
|
|
__bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
|
|
|
|
__ev1, __ev2)
|
2009-09-13 02:22:12 +08:00
|
|
|
|
|
|
|
#define TPV_QL0(__id1, __id2, __bv1, __bv2) \
|
|
|
|
TPV_Q(PCI_VENDOR_ID_LENOVO, __id1, __id2, __bv1, __bv2)
|
|
|
|
|
|
|
|
#define TPV_QL1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_LENOVO, __id1, __id2, \
|
2009-11-18 06:07:22 +08:00
|
|
|
__bv1, __bv2, TPID(__id1, __id2), \
|
|
|
|
__ev1, __ev2)
|
2009-09-13 02:22:12 +08:00
|
|
|
|
|
|
|
#define TPV_QL2(__bid1, __bid2, __bv1, __bv2, \
|
|
|
|
__eid1, __eid2, __ev1, __ev2) \
|
|
|
|
TPV_Q_X(PCI_VENDOR_ID_LENOVO, __bid1, __bid2, \
|
2009-11-18 06:07:22 +08:00
|
|
|
__bv1, __bv2, TPID(__eid1, __eid2), \
|
|
|
|
__ev1, __ev2)
|
2009-09-13 02:22:12 +08:00
|
|
|
|
|
|
|
static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
|
|
|
|
/* Numeric models ------------------ */
|
|
|
|
/* FW MODEL BIOS VERS */
|
|
|
|
TPV_QI0('I', 'M', '6', '5'), /* 570 */
|
|
|
|
TPV_QI0('I', 'U', '2', '6'), /* 570E */
|
|
|
|
TPV_QI0('I', 'B', '5', '4'), /* 600 */
|
|
|
|
TPV_QI0('I', 'H', '4', '7'), /* 600E */
|
|
|
|
TPV_QI0('I', 'N', '3', '6'), /* 600E */
|
|
|
|
TPV_QI0('I', 'T', '5', '5'), /* 600X */
|
|
|
|
TPV_QI0('I', 'D', '4', '8'), /* 770, 770E, 770ED */
|
|
|
|
TPV_QI0('I', 'I', '4', '2'), /* 770X */
|
|
|
|
TPV_QI0('I', 'O', '2', '3'), /* 770Z */
|
|
|
|
|
|
|
|
/* A-series ------------------------- */
|
|
|
|
/* FW MODEL BIOS VERS EC VERS */
|
|
|
|
TPV_QI0('I', 'W', '5', '9'), /* A20m */
|
|
|
|
TPV_QI0('I', 'V', '6', '9'), /* A20p */
|
|
|
|
TPV_QI0('1', '0', '2', '6'), /* A21e, A22e */
|
|
|
|
TPV_QI0('K', 'U', '3', '6'), /* A21e */
|
|
|
|
TPV_QI0('K', 'X', '3', '6'), /* A21m, A22m */
|
|
|
|
TPV_QI0('K', 'Y', '3', '8'), /* A21p, A22p */
|
|
|
|
TPV_QI0('1', 'B', '1', '7'), /* A22e */
|
|
|
|
TPV_QI0('1', '3', '2', '0'), /* A22m */
|
|
|
|
TPV_QI0('1', 'E', '7', '3'), /* A30/p (0) */
|
|
|
|
TPV_QI1('1', 'G', '4', '1', '1', '7'), /* A31/p (0) */
|
|
|
|
TPV_QI1('1', 'N', '1', '6', '0', '7'), /* A31/p (0) */
|
|
|
|
|
|
|
|
/* G-series ------------------------- */
|
|
|
|
/* FW MODEL BIOS VERS */
|
|
|
|
TPV_QI0('1', 'T', 'A', '6'), /* G40 */
|
|
|
|
TPV_QI0('1', 'X', '5', '7'), /* G41 */
|
|
|
|
|
|
|
|
/* R-series, T-series --------------- */
|
|
|
|
/* FW MODEL BIOS VERS EC VERS */
|
|
|
|
TPV_QI0('1', 'C', 'F', '0'), /* R30 */
|
|
|
|
TPV_QI0('1', 'F', 'F', '1'), /* R31 */
|
|
|
|
TPV_QI0('1', 'M', '9', '7'), /* R32 */
|
|
|
|
TPV_QI0('1', 'O', '6', '1'), /* R40 */
|
|
|
|
TPV_QI0('1', 'P', '6', '5'), /* R40 */
|
|
|
|
TPV_QI0('1', 'S', '7', '0'), /* R40e */
|
|
|
|
TPV_QI1('1', 'R', 'D', 'R', '7', '1'), /* R50/p, R51,
|
|
|
|
T40/p, T41/p, T42/p (1) */
|
|
|
|
TPV_QI1('1', 'V', '7', '1', '2', '8'), /* R50e, R51 (1) */
|
|
|
|
TPV_QI1('7', '8', '7', '1', '0', '6'), /* R51e (1) */
|
|
|
|
TPV_QI1('7', '6', '6', '9', '1', '6'), /* R52 (1) */
|
|
|
|
TPV_QI1('7', '0', '6', '9', '2', '8'), /* R52, T43 (1) */
|
|
|
|
|
|
|
|
TPV_QI0('I', 'Y', '6', '1'), /* T20 */
|
|
|
|
TPV_QI0('K', 'Z', '3', '4'), /* T21 */
|
|
|
|
TPV_QI0('1', '6', '3', '2'), /* T22 */
|
|
|
|
TPV_QI1('1', 'A', '6', '4', '2', '3'), /* T23 (0) */
|
|
|
|
TPV_QI1('1', 'I', '7', '1', '2', '0'), /* T30 (0) */
|
|
|
|
TPV_QI1('1', 'Y', '6', '5', '2', '9'), /* T43/p (1) */
|
|
|
|
|
|
|
|
TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */
|
|
|
|
TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */
|
2009-12-09 09:36:23 +08:00
|
|
|
TPV_QL1('7', 'E', 'D', '0', '1', '5'), /* R60e, R60i */
|
2009-09-13 02:22:12 +08:00
|
|
|
|
|
|
|
/* BIOS FW BIOS VERS EC FW EC VERS */
|
|
|
|
TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */
|
|
|
|
TPV_QL2('7', 'I', '3', '4', '7', '9', '5', '0'), /* T60/p wide */
|
|
|
|
|
|
|
|
/* X-series ------------------------- */
|
|
|
|
/* FW MODEL BIOS VERS EC VERS */
|
|
|
|
TPV_QI0('I', 'Z', '9', 'D'), /* X20, X21 */
|
|
|
|
TPV_QI0('1', 'D', '7', '0'), /* X22, X23, X24 */
|
|
|
|
TPV_QI1('1', 'K', '4', '8', '1', '8'), /* X30 (0) */
|
|
|
|
TPV_QI1('1', 'Q', '9', '7', '2', '3'), /* X31, X32 (0) */
|
|
|
|
TPV_QI1('1', 'U', 'D', '3', 'B', '2'), /* X40 (0) */
|
|
|
|
TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */
|
|
|
|
TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */
|
|
|
|
|
2009-12-09 09:36:23 +08:00
|
|
|
TPV_QL1('7', 'B', 'D', '7', '4', '0'), /* X60/s */
|
|
|
|
TPV_QL1('7', 'J', '3', '0', '1', '3'), /* X60t */
|
2009-09-13 02:22:12 +08:00
|
|
|
|
|
|
|
/* (0) - older versions lack DMI EC fw string and functionality */
|
|
|
|
/* (1) - older versions known to lack functionality */
|
|
|
|
};
|
|
|
|
|
|
|
|
#undef TPV_QL1
|
|
|
|
#undef TPV_QL0
|
|
|
|
#undef TPV_QI2
|
|
|
|
#undef TPV_QI1
|
|
|
|
#undef TPV_QI0
|
|
|
|
#undef TPV_Q_X
|
|
|
|
#undef TPV_Q
|
|
|
|
|
|
|
|
static void __init tpacpi_check_outdated_fw(void)
|
|
|
|
{
|
|
|
|
unsigned long fwvers;
|
|
|
|
u16 ec_version, bios_version;
|
|
|
|
|
|
|
|
fwvers = tpacpi_check_quirks(tpacpi_bios_version_qtable,
|
|
|
|
ARRAY_SIZE(tpacpi_bios_version_qtable));
|
|
|
|
|
|
|
|
if (!fwvers)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bios_version = fwvers & 0xffffU;
|
|
|
|
ec_version = (fwvers >> 16) & 0xffffU;
|
|
|
|
|
|
|
|
/* note that unknown versions are set to 0x0000 and we use that */
|
|
|
|
if ((bios_version > thinkpad_id.bios_release) ||
|
|
|
|
(ec_version > thinkpad_id.ec_release &&
|
|
|
|
ec_version != TPACPI_MATCH_ANY)) {
|
|
|
|
/*
|
|
|
|
* The changelogs would let us track down the exact
|
|
|
|
* reason, but it is just too much of a pain to track
|
|
|
|
* it. We only list BIOSes that are either really
|
|
|
|
* broken, or really stable to begin with, so it is
|
|
|
|
* best if the user upgrades the firmware anyway.
|
|
|
|
*/
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_warn("WARNING: Outdated ThinkPad BIOS/EC firmware\n");
|
|
|
|
pr_warn("WARNING: This firmware may be missing critical bug "
|
2009-09-13 02:22:12 +08:00
|
|
|
"fixes and/or important features\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-13 02:22:13 +08:00
|
|
|
static bool __init tpacpi_is_fw_known(void)
|
|
|
|
{
|
|
|
|
return tpacpi_check_quirks(tpacpi_bios_version_qtable,
|
|
|
|
ARRAY_SIZE(tpacpi_bios_version_qtable)) != 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Subdrivers
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/*************************************************************************
|
2010-05-17 06:45:31 +08:00
|
|
|
* thinkpad-acpi metadata subdriver
|
2008-01-08 23:02:45 +08:00
|
|
|
*/
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int thinkpad_acpi_driver_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
|
|
|
|
seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct thinkpad_acpi_driver_data = {
|
|
|
|
.name = "driver",
|
|
|
|
.read = thinkpad_acpi_driver_read,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Hotkey subdriver
|
|
|
|
*/
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/*
|
|
|
|
* ThinkPad firmware event model
|
|
|
|
*
|
|
|
|
* The ThinkPad firmware has two main event interfaces: normal ACPI
|
|
|
|
* notifications (which follow the ACPI standard), and a private event
|
|
|
|
* interface.
|
|
|
|
*
|
|
|
|
* The private event interface also issues events for the hotkeys. As
|
|
|
|
* the driver gained features, the event handling code ended up being
|
|
|
|
* built around the hotkey subdriver. This will need to be refactored
|
|
|
|
* to a more formal event API eventually.
|
|
|
|
*
|
|
|
|
* Some "hotkeys" are actually supposed to be used as event reports,
|
|
|
|
* such as "brightness has changed", "volume has changed", depending on
|
|
|
|
* the ThinkPad model and how the firmware is operating.
|
|
|
|
*
|
|
|
|
* Unlike other classes, hotkey-class events have mask/unmask control on
|
|
|
|
* non-ancient firmware. However, how it behaves changes a lot with the
|
|
|
|
* firmware model and version.
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
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,
|
2010-08-10 10:48:20 +08:00
|
|
|
TP_ACPI_HOTKEYSCAN_UNK1,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK2,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK3,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK4,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK5,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK6,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK7,
|
|
|
|
TP_ACPI_HOTKEYSCAN_UNK8,
|
|
|
|
|
|
|
|
/* Hotkey keymap size */
|
|
|
|
TPACPI_HOTKEY_MAP_LEN
|
2008-01-08 23:02:44 +08:00
|
|
|
};
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
enum { /* Keys/events available through NVRAM polling */
|
2008-01-08 23:02:41 +08:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
/* kthread for the hotkey poller */
|
2008-01-08 23:02:41 +08:00
|
|
|
static struct task_struct *tpacpi_hotkey_task;
|
2009-09-13 02:22:14 +08:00
|
|
|
|
|
|
|
/* Acquired while the poller kthread is running, use to sync start/stop */
|
2008-01-08 23:02:41 +08:00
|
|
|
static struct mutex hotkey_thread_mutex;
|
2009-09-13 02:22:14 +08:00
|
|
|
|
|
|
|
/*
|
2009-09-21 01:09:25 +08:00
|
|
|
* Acquire mutex to write poller control variables as an
|
|
|
|
* atomic block.
|
|
|
|
*
|
|
|
|
* Increment hotkey_config_change when changing them if you
|
|
|
|
* want the kthread to forget old state.
|
2009-09-13 02:22:14 +08:00
|
|
|
*
|
|
|
|
* See HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
*/
|
2008-01-08 23:02:41 +08:00
|
|
|
static struct mutex hotkey_thread_data_mutex;
|
|
|
|
static unsigned int hotkey_config_change;
|
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
/*
|
|
|
|
* hotkey poller control variables
|
|
|
|
*
|
|
|
|
* Must be atomic or readers will also need to acquire mutex
|
2009-09-21 01:09:25 +08:00
|
|
|
*
|
|
|
|
* HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
* should be used only when the changes need to be taken as
|
|
|
|
* a block, OR when one needs to force the kthread to forget
|
|
|
|
* old state.
|
2009-09-13 02:22:14 +08:00
|
|
|
*/
|
|
|
|
static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */
|
|
|
|
static unsigned int hotkey_poll_freq = 10; /* Hz */
|
|
|
|
|
|
|
|
#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);
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
|
|
|
#define hotkey_source_mask 0U
|
2009-09-13 02:22:14 +08:00
|
|
|
#define HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
#define HOTKEY_CONFIG_CRITICAL_END
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
static struct mutex hotkey_mutex;
|
|
|
|
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
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;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
static u32 hotkey_orig_mask; /* events the BIOS had enabled */
|
|
|
|
static u32 hotkey_all_mask; /* all events supported in fw */
|
|
|
|
static u32 hotkey_reserved_mask; /* events better left disabled */
|
|
|
|
static u32 hotkey_driver_mask; /* events needed by the driver */
|
|
|
|
static u32 hotkey_user_mask; /* events visible to userspace */
|
|
|
|
static u32 hotkey_acpi_mask; /* events enabled in firmware */
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static unsigned int hotkey_report_mode;
|
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
static u16 *hotkey_keycode_map;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct attribute_set *hotkey_dev_attributes;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2009-09-21 01:09:26 +08:00
|
|
|
static void tpacpi_driver_event(const unsigned int hkey_event);
|
|
|
|
static void hotkey_driver_event(const unsigned int scancode);
|
2010-02-26 08:29:00 +08:00
|
|
|
static void hotkey_poll_setup(const bool may_warn);
|
2009-09-21 01:09:26 +08:00
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
/* HKEY.MHKG() return bits */
|
|
|
|
#define TP_HOTKEY_TABLET_MASK (1 << 3)
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int hotkey_get_wlsw(void)
|
2007-07-19 10:45:31 +08:00
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!tp_features.hotkey_wlsw)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
2009-06-02 19:01:37 +08:00
|
|
|
if (dbg_wlswemul)
|
|
|
|
return (tpacpi_wlsw_emulstate) ?
|
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
if (!acpi_evalf(hkey_handle, &status, "WLSW", "d"))
|
2007-07-19 10:45:31 +08:00
|
|
|
return -EIO;
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2007-07-19 10:45:31 +08:00
|
|
|
}
|
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
static int hotkey_get_tablet_mode(int *status)
|
|
|
|
{
|
|
|
|
int s;
|
|
|
|
|
|
|
|
if (!acpi_evalf(hkey_handle, &s, "MHKG", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
2008-03-05 06:29:21 +08:00
|
|
|
*status = ((s & TP_HOTKEY_TABLET_MASK) != 0);
|
|
|
|
return 0;
|
2008-02-16 12:17:58 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
/*
|
2009-09-21 01:09:25 +08:00
|
|
|
* Reads current event mask from firmware, and updates
|
|
|
|
* hotkey_acpi_mask accordingly. Also resets any bits
|
|
|
|
* from hotkey_user_mask that are unavailable to be
|
|
|
|
* delivered (shadow requirement of the userspace ABI).
|
|
|
|
*
|
2008-01-08 23:02:39 +08:00
|
|
|
* Call with hotkey_mutex held
|
|
|
|
*/
|
|
|
|
static int hotkey_mask_get(void)
|
|
|
|
{
|
|
|
|
if (tp_features.hotkey_mask) {
|
2009-09-21 01:09:25 +08:00
|
|
|
u32 m = 0;
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
|
2008-01-08 23:02:39 +08:00
|
|
|
return -EIO;
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
hotkey_acpi_mask = m;
|
|
|
|
} else {
|
|
|
|
/* no mask support doesn't mean no event support... */
|
|
|
|
hotkey_acpi_mask = hotkey_all_mask;
|
2008-01-08 23:02:39 +08:00
|
|
|
}
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
/* sync userspace-visible mask */
|
|
|
|
hotkey_user_mask &= (hotkey_acpi_mask | hotkey_source_mask);
|
2008-01-08 23:02:39 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
void static hotkey_mask_warn_incomplete_mask(void)
|
|
|
|
{
|
|
|
|
/* log only what the user can fix... */
|
|
|
|
const u32 wantedmask = hotkey_driver_mask &
|
|
|
|
~(hotkey_acpi_mask | hotkey_source_mask) &
|
|
|
|
(hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK);
|
|
|
|
|
|
|
|
if (wantedmask)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("required events 0x%08x not enabled!\n", wantedmask);
|
2009-09-21 01:09:25 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
/*
|
2009-09-21 01:09:25 +08:00
|
|
|
* Set the firmware mask when supported
|
|
|
|
*
|
|
|
|
* Also calls hotkey_mask_get to update hotkey_acpi_mask.
|
|
|
|
*
|
|
|
|
* NOTE: does not set bits in hotkey_user_mask, but may reset them.
|
|
|
|
*
|
2008-01-08 23:02:39 +08:00
|
|
|
* Call with hotkey_mutex held
|
|
|
|
*/
|
|
|
|
static int hotkey_mask_set(u32 mask)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int rc = 0;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
const u32 fwmask = mask & ~hotkey_source_mask;
|
ACPI: thinkpad-acpi: warn once about weird hotkey masks
thinkpad-acpi knows for a while now how to best program the hotkeys by
default, and always enable them by default. Unfortunately, this
information has not filtered down everywhere it needs to, yet. Notably,
old ibm-acpi documentation and most "thinkpad setup guides" will have wrong
information on this area.
Warn the local admin once whenever any of the following patterns are met:
1. Attempts to set hotkey mask to 0xffff (artifact from docs and config
for the old ibm-acpi driver and behaviour). This mask makes no
real-world sense;
2. Attempts to set hotkey mask to 0xffffffff, which means the user is
trying to just have "everything work" without even reading the
documentation, or that we need to get a bug report, because there
is a new thinkpad out there with new exciting hot keys :-)
3. Attempts to set hotkey mask to 0xffffff, which is almost never the
correct way to set up volume and brightness event reporting (and with
the current state-of-the-art, it is known to never be right way to do
it).
The driver will perform any and all requested operations, though,
regardless of any warnings. I hope these warnings can be removed one or
two years from now.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-04-26 12:02:18 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (tp_features.hotkey_mask) {
|
2008-01-08 23:02:39 +08:00
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
if (!acpi_evalf(hkey_handle,
|
|
|
|
NULL, "MHKM", "vdd", i + 1,
|
2009-09-21 01:09:25 +08:00
|
|
|
!!(mask & (1 << i)))) {
|
2008-01-08 23:02:39 +08:00
|
|
|
rc = -EIO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2009-09-21 01:09:25 +08:00
|
|
|
}
|
2008-01-08 23:02:39 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/*
|
|
|
|
* We *must* make an inconditional call to hotkey_mask_get to
|
|
|
|
* refresh hotkey_acpi_mask and update hotkey_user_mask
|
|
|
|
*
|
|
|
|
* Take the opportunity to also log when we cannot _enable_
|
|
|
|
* a given event.
|
|
|
|
*/
|
|
|
|
if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("asked for hotkey mask 0x%08x, but "
|
|
|
|
"firmware forced it to 0x%08x\n",
|
|
|
|
fwmask, hotkey_acpi_mask);
|
2008-01-08 23:02:39 +08:00
|
|
|
}
|
|
|
|
|
2009-12-09 09:36:25 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
|
|
|
|
hotkey_mask_warn_incomplete_mask();
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sets hotkey_user_mask and tries to set the firmware mask
|
|
|
|
*
|
|
|
|
* Call with hotkey_mutex held
|
|
|
|
*/
|
|
|
|
static int hotkey_user_mask_set(const u32 mask)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Give people a chance to notice they are doing something that
|
|
|
|
* is bound to go boom on their users sooner or later */
|
|
|
|
if (!tp_warned.hotkey_mask_ff &&
|
|
|
|
(mask == 0xffff || mask == 0xffffff ||
|
|
|
|
mask == 0xffffffff)) {
|
|
|
|
tp_warned.hotkey_mask_ff = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("setting the hotkey mask to 0x%08x is likely "
|
|
|
|
"not the best way to go about it\n", mask);
|
|
|
|
pr_notice("please consider using the driver defaults, "
|
|
|
|
"and refer to up-to-date thinkpad-acpi "
|
|
|
|
"documentation\n");
|
2009-09-21 01:09:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to enable what the user asked for, plus whatever we need.
|
|
|
|
* this syncs everything but won't enable bits in hotkey_user_mask */
|
|
|
|
rc = hotkey_mask_set((mask | hotkey_driver_mask) & ~hotkey_source_mask);
|
|
|
|
|
|
|
|
/* Enable the available bits in hotkey_user_mask */
|
|
|
|
hotkey_user_mask = mask & (hotkey_acpi_mask | hotkey_source_mask);
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:26 +08:00
|
|
|
/*
|
|
|
|
* Sets the driver hotkey mask.
|
|
|
|
*
|
|
|
|
* Can be called even if the hotkey subdriver is inactive
|
|
|
|
*/
|
|
|
|
static int tpacpi_hotkey_driver_mask_set(const u32 mask)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Do the right thing if hotkey_init has not been called yet */
|
|
|
|
if (!tp_features.hotkey) {
|
|
|
|
hotkey_driver_mask = mask;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&hotkey_mutex);
|
|
|
|
|
|
|
|
HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
hotkey_driver_mask = mask;
|
2009-09-27 08:42:49 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
2009-09-21 01:09:26 +08:00
|
|
|
hotkey_source_mask |= (mask & ~hotkey_all_mask);
|
2009-09-27 08:42:49 +08:00
|
|
|
#endif
|
2009-09-21 01:09:26 +08:00
|
|
|
HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
|
|
|
|
rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) &
|
|
|
|
~hotkey_source_mask);
|
2010-02-26 08:29:00 +08:00
|
|
|
hotkey_poll_setup(true);
|
|
|
|
|
2009-09-21 01:09:26 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
static int hotkey_status_get(int *status)
|
|
|
|
{
|
|
|
|
if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:48 +08:00
|
|
|
static int hotkey_status_set(bool enable)
|
2008-01-08 23:02:39 +08:00
|
|
|
{
|
2009-04-04 12:25:48 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", enable ? 1 : 0))
|
2008-01-08 23:02:39 +08:00
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
static void tpacpi_input_send_tabletsw(void)
|
2008-02-16 12:17:55 +08:00
|
|
|
{
|
2008-02-16 12:17:58 +08:00
|
|
|
int state;
|
2008-02-16 12:17:55 +08:00
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
if (tp_features.hotkey_tablet &&
|
|
|
|
!hotkey_get_tablet_mode(&state)) {
|
|
|
|
mutex_lock(&tpacpi_inputdev_send_mutex);
|
2008-02-16 12:17:55 +08:00
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
input_report_switch(tpacpi_inputdev,
|
|
|
|
SW_TABLET_MODE, !!state);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
|
|
|
mutex_unlock(&tpacpi_inputdev_send_mutex);
|
|
|
|
}
|
2008-02-16 12:17:55 +08:00
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* Do NOT call without validating scancode first */
|
|
|
|
static void tpacpi_input_send_key(const unsigned int scancode)
|
2008-01-08 23:02:40 +08:00
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
const unsigned int keycode = hotkey_keycode_map[scancode];
|
2008-01-08 23:02:40 +08:00
|
|
|
|
|
|
|
if (keycode != KEY_RESERVED) {
|
|
|
|
mutex_lock(&tpacpi_inputdev_send_mutex);
|
|
|
|
|
2011-01-15 05:54:39 +08:00
|
|
|
input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
|
2008-01-08 23:02:40 +08:00
|
|
|
input_report_key(tpacpi_inputdev, keycode, 1);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
2011-01-15 05:54:39 +08:00
|
|
|
input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
|
2008-01-08 23:02:40 +08:00
|
|
|
input_report_key(tpacpi_inputdev, keycode, 0);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
|
|
|
mutex_unlock(&tpacpi_inputdev_send_mutex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* Do NOT call without validating scancode first */
|
|
|
|
static void tpacpi_input_send_key_masked(const unsigned int scancode)
|
|
|
|
{
|
2009-09-21 01:09:26 +08:00
|
|
|
hotkey_driver_event(scancode);
|
2009-09-21 01:09:25 +08:00
|
|
|
if (hotkey_user_mask & (1 << scancode))
|
|
|
|
tpacpi_input_send_key(scancode);
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* Do NOT call without validating scancode first */
|
2008-01-08 23:02:41 +08:00
|
|
|
static void tpacpi_hotkey_send_key(unsigned int scancode)
|
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
tpacpi_input_send_key_masked(scancode);
|
2008-01-08 23:02:41 +08:00
|
|
|
if (hotkey_report_mode < 2) {
|
|
|
|
acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
|
2009-09-21 01:09:27 +08:00
|
|
|
0x80, TP_HKEY_EV_HOTKEY_BASE + scancode);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)
|
2008-01-08 23:02:41 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
|
|
|
|
struct tp_nvram_state *newn,
|
|
|
|
const u32 event_mask)
|
|
|
|
{
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#define TPACPI_COMPARE_KEY(__scancode, __member) \
|
2008-01-08 23:02:49 +08:00
|
|
|
do { \
|
2009-09-21 01:09:25 +08:00
|
|
|
if ((event_mask & (1 << __scancode)) && \
|
2008-01-08 23:02:49 +08:00
|
|
|
oldn->__member != newn->__member) \
|
2009-09-21 01:09:25 +08:00
|
|
|
tpacpi_hotkey_send_key(__scancode); \
|
2008-01-08 23:02:49 +08:00
|
|
|
} while (0)
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
#define TPACPI_MAY_SEND_KEY(__scancode) \
|
2009-09-21 01:09:25 +08:00
|
|
|
do { \
|
|
|
|
if (event_mask & (1 << __scancode)) \
|
|
|
|
tpacpi_hotkey_send_key(__scancode); \
|
|
|
|
} while (0)
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2010-05-17 06:45:28 +08:00
|
|
|
void issue_volchange(const unsigned int oldvol,
|
|
|
|
const unsigned int newvol)
|
|
|
|
{
|
|
|
|
unsigned int i = oldvol;
|
|
|
|
|
|
|
|
while (i > newvol) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
while (i < newvol) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-17 06:45:36 +08:00
|
|
|
void issue_brightnesschange(const unsigned int oldbrt,
|
|
|
|
const unsigned int newbrt)
|
|
|
|
{
|
|
|
|
unsigned int i = oldbrt;
|
|
|
|
|
|
|
|
while (i > newbrt) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
while (i < newbrt) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
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);
|
|
|
|
|
2010-05-17 06:45:28 +08:00
|
|
|
/*
|
|
|
|
* Handle volume
|
|
|
|
*
|
|
|
|
* This code is supposed to duplicate the IBM firmware behaviour:
|
|
|
|
* - Pressing MUTE issues mute hotkey message, even when already mute
|
|
|
|
* - Pressing Volume up/down issues volume up/down hotkey messages,
|
2011-03-18 04:18:22 +08:00
|
|
|
* even when already at maximum or minimum volume
|
2010-05-17 06:45:28 +08:00
|
|
|
* - The act of unmuting issues volume up/down notification,
|
|
|
|
* depending which key was used to unmute
|
|
|
|
*
|
|
|
|
* We are constrained to what the NVRAM can tell us, which is not much
|
|
|
|
* and certainly not enough if more than one volume hotkey was pressed
|
|
|
|
* since the last poll cycle.
|
|
|
|
*
|
|
|
|
* Just to make our life interesting, some newer Lenovo ThinkPads have
|
|
|
|
* bugs in the BIOS and may fail to update volume_toggle properly.
|
|
|
|
*/
|
|
|
|
if (newn->mute) {
|
|
|
|
/* muted */
|
|
|
|
if (!oldn->mute ||
|
|
|
|
oldn->volume_toggle != newn->volume_toggle ||
|
|
|
|
oldn->volume_level != newn->volume_level) {
|
|
|
|
/* recently muted, or repeated mute keypress, or
|
|
|
|
* multiple presses ending in mute */
|
|
|
|
issue_volchange(oldn->volume_level, newn->volume_level);
|
2008-01-08 23:02:41 +08:00
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
|
|
|
|
}
|
2010-05-17 06:45:28 +08:00
|
|
|
} else {
|
|
|
|
/* unmute */
|
|
|
|
if (oldn->mute) {
|
|
|
|
/* recently unmuted, issue 'unmute' keypress */
|
2008-01-08 23:02:41 +08:00
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
2010-05-17 06:45:28 +08:00
|
|
|
}
|
|
|
|
if (oldn->volume_level != newn->volume_level) {
|
|
|
|
issue_volchange(oldn->volume_level, newn->volume_level);
|
|
|
|
} else if (oldn->volume_toggle != newn->volume_toggle) {
|
|
|
|
/* repeated vol up/down keypress at end of scale ? */
|
|
|
|
if (newn->volume_level == 0)
|
2008-01-08 23:02:41 +08:00
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
2010-05-17 06:45:28 +08:00
|
|
|
else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* handle brightness */
|
2010-05-17 06:45:36 +08:00
|
|
|
if (oldn->brightness_level != newn->brightness_level) {
|
|
|
|
issue_brightnesschange(oldn->brightness_level,
|
|
|
|
newn->brightness_level);
|
|
|
|
} else if (oldn->brightness_toggle != newn->brightness_toggle) {
|
|
|
|
/* repeated key presses that didn't change state */
|
|
|
|
if (newn->brightness_level == 0)
|
2008-01-08 23:02:41 +08:00
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
2010-05-17 06:45:36 +08:00
|
|
|
else if (newn->brightness_level >= bright_maxlvl
|
|
|
|
&& !tp_features.bright_unkfw)
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#undef TPACPI_COMPARE_KEY
|
|
|
|
#undef TPACPI_MAY_SEND_KEY
|
2009-09-21 01:09:25 +08:00
|
|
|
}
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/*
|
|
|
|
* Polling driver
|
|
|
|
*
|
|
|
|
* We track all events in hotkey_source_mask all the time, since
|
|
|
|
* most of them are edge-based. We only issue those requested by
|
|
|
|
* hotkey_user_mask or hotkey_driver_mask, though.
|
|
|
|
*/
|
2008-01-08 23:02:41 +08:00
|
|
|
static int hotkey_kthread(void *data)
|
|
|
|
{
|
|
|
|
struct tp_nvram_state s[2];
|
2009-09-21 01:09:25 +08:00
|
|
|
u32 poll_mask, event_mask;
|
2008-01-08 23:02:41 +08:00
|
|
|
unsigned int si, so;
|
|
|
|
unsigned long t;
|
2011-11-22 04:32:23 +08:00
|
|
|
unsigned int change_detector;
|
2009-09-13 02:22:14 +08:00
|
|
|
unsigned int poll_freq;
|
2011-11-22 04:32:23 +08:00
|
|
|
bool was_frozen;
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
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;
|
2009-09-21 01:09:25 +08:00
|
|
|
poll_mask = hotkey_source_mask;
|
|
|
|
event_mask = hotkey_source_mask &
|
|
|
|
(hotkey_driver_mask | hotkey_user_mask);
|
2009-09-13 02:22:14 +08:00
|
|
|
poll_freq = hotkey_poll_freq;
|
2008-01-08 23:02:41 +08:00
|
|
|
mutex_unlock(&hotkey_thread_data_mutex);
|
2009-09-21 01:09:25 +08:00
|
|
|
hotkey_read_nvram(&s[so], poll_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
while (!kthread_should_stop()) {
|
|
|
|
if (t == 0) {
|
|
|
|
if (likely(poll_freq))
|
|
|
|
t = 1000/poll_freq;
|
|
|
|
else
|
|
|
|
t = 100; /* should never happen... */
|
|
|
|
}
|
2008-01-08 23:02:41 +08:00
|
|
|
t = msleep_interruptible(t);
|
2011-11-22 04:32:23 +08:00
|
|
|
if (unlikely(kthread_freezable_should_stop(&was_frozen)))
|
2008-01-08 23:02:41 +08:00
|
|
|
break;
|
2011-11-22 04:32:23 +08:00
|
|
|
|
|
|
|
if (t > 0 && !was_frozen)
|
2008-01-08 23:02:41 +08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
mutex_lock(&hotkey_thread_data_mutex);
|
2011-11-22 04:32:23 +08:00
|
|
|
if (was_frozen || hotkey_config_change != change_detector) {
|
2008-01-08 23:02:41 +08:00
|
|
|
/* forget old state on thaw or config change */
|
|
|
|
si = so;
|
|
|
|
t = 0;
|
|
|
|
change_detector = hotkey_config_change;
|
|
|
|
}
|
2009-09-21 01:09:25 +08:00
|
|
|
poll_mask = hotkey_source_mask;
|
|
|
|
event_mask = hotkey_source_mask &
|
|
|
|
(hotkey_driver_mask | hotkey_user_mask);
|
2009-09-13 02:22:14 +08:00
|
|
|
poll_freq = hotkey_poll_freq;
|
2008-01-08 23:02:41 +08:00
|
|
|
mutex_unlock(&hotkey_thread_data_mutex);
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (likely(poll_mask)) {
|
|
|
|
hotkey_read_nvram(&s[si], poll_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
if (likely(si != so)) {
|
|
|
|
hotkey_compare_and_issue_event(&s[so], &s[si],
|
2009-09-21 01:09:25 +08:00
|
|
|
event_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
so = si;
|
|
|
|
si ^= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&hotkey_thread_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
/* call with hotkey_mutex held */
|
2008-01-08 23:02:41 +08:00
|
|
|
static void hotkey_poll_stop_sync(void)
|
|
|
|
{
|
|
|
|
if (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 */
|
2010-02-26 08:29:00 +08:00
|
|
|
static void hotkey_poll_setup(const bool may_warn)
|
2008-01-08 23:02:41 +08:00
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
|
|
|
|
const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
|
2009-09-13 02:22:14 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (hotkey_poll_freq > 0 &&
|
|
|
|
(poll_driver_mask ||
|
|
|
|
(poll_user_mask && tpacpi_inputdev->users > 0))) {
|
2008-01-08 23:02:41 +08:00
|
|
|
if (!tpacpi_hotkey_task) {
|
|
|
|
tpacpi_hotkey_task = kthread_run(hotkey_kthread,
|
2008-04-26 12:02:22 +08:00
|
|
|
NULL, TPACPI_NVRAM_KTHREAD_NAME);
|
2008-01-08 23:02:41 +08:00
|
|
|
if (IS_ERR(tpacpi_hotkey_task)) {
|
|
|
|
tpacpi_hotkey_task = NULL;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("could not create kernel thread "
|
2008-01-08 23:02:41 +08:00
|
|
|
"for hotkey polling\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hotkey_poll_stop_sync();
|
2009-09-21 01:09:25 +08:00
|
|
|
if (may_warn && (poll_driver_mask || poll_user_mask) &&
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_freq == 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("hot keys 0x%08x and/or events 0x%08x "
|
|
|
|
"require polling, which is currently "
|
|
|
|
"disabled\n",
|
|
|
|
poll_user_mask, poll_driver_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-26 08:29:00 +08:00
|
|
|
static void hotkey_poll_setup_safe(const bool may_warn)
|
2008-01-08 23:02:41 +08:00
|
|
|
{
|
|
|
|
mutex_lock(&hotkey_mutex);
|
|
|
|
hotkey_poll_setup(may_warn);
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
}
|
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
/* call with hotkey_mutex held */
|
|
|
|
static void hotkey_poll_set_freq(unsigned int freq)
|
|
|
|
{
|
|
|
|
if (!freq)
|
|
|
|
hotkey_poll_stop_sync();
|
|
|
|
|
|
|
|
hotkey_poll_freq = freq;
|
|
|
|
}
|
|
|
|
|
2008-02-16 12:17:52 +08:00
|
|
|
#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2010-02-26 08:29:00 +08:00
|
|
|
static void hotkey_poll_setup(const bool __unused)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_poll_setup_safe(const bool __unused)
|
2008-02-16 12:17:52 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
static int hotkey_inputdev_open(struct input_dev *dev)
|
|
|
|
{
|
|
|
|
switch (tpacpi_lifecycle) {
|
|
|
|
case TPACPI_LIFE_INIT:
|
|
|
|
case TPACPI_LIFE_RUNNING:
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup_safe(false);
|
2008-01-08 23:02:41 +08:00
|
|
|
return 0;
|
2010-02-26 08:28:58 +08:00
|
|
|
case TPACPI_LIFE_EXITING:
|
|
|
|
return -EBUSY;
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 */
|
2010-02-26 08:28:58 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_EXITING &&
|
2009-09-21 01:09:25 +08:00
|
|
|
!(hotkey_source_mask & hotkey_driver_mask))
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup_safe(false);
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* sysfs hotkey enable ------------------------------------------------- */
|
|
|
|
static ssize_t hotkey_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
int res, status;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2009-04-04 12:25:48 +08:00
|
|
|
printk_deprecated_attribute("hotkey_enable",
|
|
|
|
"Hotkey reporting is always enabled");
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_get(&status);
|
2007-04-28 09:00:16 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", status);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
2009-04-04 12:25:48 +08:00
|
|
|
|
|
|
|
printk_deprecated_attribute("hotkey_enable",
|
|
|
|
"Hotkeys can be disabled through hotkey_mask");
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-04 12:25:48 +08:00
|
|
|
if (t == 0)
|
|
|
|
return -EPERM;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2009-04-04 12:25:48 +08:00
|
|
|
return count;
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:16 +08:00
|
|
|
hotkey_enable_show, hotkey_enable_store);
|
|
|
|
|
|
|
|
/* sysfs hotkey mask --------------------------------------------------- */
|
|
|
|
static ssize_t hotkey_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_user_mask);
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_mask_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
2008-01-08 23:02:39 +08:00
|
|
|
int res;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2007-07-19 10:45:29 +08:00
|
|
|
if (parse_strtoul(buf, 0xffffffffUL, &t))
|
2007-04-28 09:00:16 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&hotkey_mutex))
|
2008-01-08 23:02:39 +08:00
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
res = hotkey_user_mask_set(t);
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup(true);
|
2008-01-08 23:02:41 +08:00
|
|
|
#endif
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t);
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
return (res) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_mask =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_mask, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:16 +08:00
|
|
|
hotkey_mask_show, hotkey_mask_store);
|
|
|
|
|
|
|
|
/* sysfs hotkey bios_enabled ------------------------------------------- */
|
|
|
|
static ssize_t hotkey_bios_enabled_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-04-04 12:25:48 +08:00
|
|
|
return sprintf(buf, "0\n");
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_bios_enabled =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
/* sysfs hotkey bios_mask ---------------------------------------------- */
|
|
|
|
static ssize_t hotkey_bios_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-09-13 02:22:15 +08:00
|
|
|
printk_deprecated_attribute("hotkey_bios_mask",
|
|
|
|
"This attribute is useless.");
|
2007-07-19 10:45:29 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_bios_mask =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2007-07-19 10:45:30 +08:00
|
|
|
/* sysfs hotkey all_mask ----------------------------------------------- */
|
|
|
|
static ssize_t hotkey_all_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2008-01-08 23:02:41 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n",
|
|
|
|
hotkey_all_mask | hotkey_source_mask);
|
2007-07-19 10:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_all_mask =
|
|
|
|
__ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL);
|
|
|
|
|
|
|
|
/* sysfs hotkey recommended_mask --------------------------------------- */
|
|
|
|
static ssize_t hotkey_recommended_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n",
|
2008-01-08 23:02:41 +08:00
|
|
|
(hotkey_all_mask | hotkey_source_mask)
|
|
|
|
& ~hotkey_reserved_mask);
|
2007-07-19 10:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_recommended_mask =
|
|
|
|
__ATTR(hotkey_recommended_mask, S_IRUGO,
|
|
|
|
hotkey_recommended_mask_show, NULL);
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#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;
|
2009-09-21 01:09:25 +08:00
|
|
|
u32 r_ev;
|
|
|
|
int rc;
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
if (parse_strtoul(buf, 0xffffffffUL, &t) ||
|
|
|
|
((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&hotkey_mutex))
|
2008-01-08 23:02:41 +08:00
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
hotkey_source_mask = t;
|
|
|
|
HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
rc = hotkey_mask_set((hotkey_user_mask | hotkey_driver_mask) &
|
|
|
|
~hotkey_source_mask);
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup(true);
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
/* check if events needed by the driver got disabled */
|
|
|
|
r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask)
|
|
|
|
& ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK;
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (rc < 0)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("hotkey_source_mask: "
|
|
|
|
"failed to update the firmware event mask!\n");
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
if (r_ev)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("hotkey_source_mask: "
|
|
|
|
"some important events were disabled: 0x%04x\n",
|
|
|
|
r_ev);
|
2009-09-21 01:09:25 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
tpacpi_disclose_usertask("hotkey_source_mask", "set to 0x%08lx\n", t);
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
return (rc < 0) ? rc : count;
|
2008-01-08 23:02:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&hotkey_mutex))
|
2008-01-08 23:02:41 +08:00
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_set_freq(t);
|
|
|
|
hotkey_poll_setup(true);
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t);
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
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 */
|
|
|
|
|
2008-01-08 23:02:55 +08:00
|
|
|
/* sysfs hotkey radio_sw (pollable) ------------------------------------ */
|
2007-07-19 10:45:31 +08:00
|
|
|
static ssize_t hotkey_radio_sw_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
int res;
|
|
|
|
res = hotkey_get_wlsw();
|
2007-07-19 10:45:31 +08:00
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
/* Opportunistic update */
|
|
|
|
tpacpi_rfk_update_hwblock_state((res == TPACPI_RFK_RADIO_OFF));
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
|
|
(res == TPACPI_RFK_RADIO_OFF) ? 0 : 1);
|
2007-07-19 10:45:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_radio_sw =
|
|
|
|
__ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL);
|
|
|
|
|
2008-01-08 23:02:55 +08:00
|
|
|
static void hotkey_radio_sw_notify_change(void)
|
|
|
|
{
|
|
|
|
if (tp_features.hotkey_wlsw)
|
|
|
|
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
|
|
|
|
"hotkey_radio_sw");
|
|
|
|
}
|
|
|
|
|
2008-02-16 12:17:58 +08:00
|
|
|
/* sysfs hotkey tablet mode (pollable) --------------------------------- */
|
|
|
|
static ssize_t hotkey_tablet_mode_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res, s;
|
|
|
|
res = hotkey_get_tablet_mode(&s);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_tablet_mode =
|
|
|
|
__ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL);
|
|
|
|
|
|
|
|
static void hotkey_tablet_mode_notify_change(void)
|
|
|
|
{
|
|
|
|
if (tp_features.hotkey_tablet)
|
|
|
|
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
|
|
|
|
"hotkey_tablet_mode");
|
|
|
|
}
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* sysfs hotkey report_mode -------------------------------------------- */
|
|
|
|
static ssize_t hotkey_report_mode_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
|
|
(hotkey_report_mode != 0) ? hotkey_report_mode : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_report_mode =
|
|
|
|
__ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL);
|
|
|
|
|
2008-01-08 23:02:55 +08:00
|
|
|
/* sysfs wakeup reason (pollable) -------------------------------------- */
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
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);
|
|
|
|
|
2008-02-14 05:30:06 +08:00
|
|
|
static void hotkey_wakeup_reason_notify_change(void)
|
2008-01-08 23:02:55 +08:00
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
|
|
|
|
"wakeup_reason");
|
2008-01-08 23:02:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* sysfs wakeup hotunplug_complete (pollable) -------------------------- */
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
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);
|
|
|
|
|
2008-02-14 05:30:06 +08:00
|
|
|
static void hotkey_wakeup_hotunplug_complete_notify_change(void)
|
2008-01-08 23:02:55 +08:00
|
|
|
{
|
2009-09-21 01:09:25 +08:00
|
|
|
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
|
|
|
|
"wakeup_hotunplug_complete");
|
2008-01-08 23:02:55 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
static struct attribute *hotkey_attributes[] __initdata = {
|
|
|
|
&dev_attr_hotkey_enable.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
&dev_attr_hotkey_bios_enabled.attr,
|
2009-09-21 01:09:25 +08:00
|
|
|
&dev_attr_hotkey_bios_mask.attr,
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
&dev_attr_hotkey_report_mode.attr,
|
2009-09-21 01:09:25 +08:00
|
|
|
&dev_attr_hotkey_wakeup_reason.attr,
|
|
|
|
&dev_attr_hotkey_wakeup_hotunplug_complete.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
&dev_attr_hotkey_mask.attr,
|
|
|
|
&dev_attr_hotkey_all_mask.attr,
|
|
|
|
&dev_attr_hotkey_recommended_mask.attr,
|
2009-09-21 01:09:25 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
2008-01-08 23:02:41 +08:00
|
|
|
&dev_attr_hotkey_source_mask.attr,
|
|
|
|
&dev_attr_hotkey_poll_freq.attr,
|
|
|
|
#endif
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
};
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
/*
|
|
|
|
* Sync both the hw and sw blocking state of all switches
|
|
|
|
*/
|
2008-07-21 20:15:49 +08:00
|
|
|
static void tpacpi_send_radiosw_update(void)
|
|
|
|
{
|
|
|
|
int wlsw;
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
/*
|
|
|
|
* We must sync all rfkill controllers *before* issuing any
|
|
|
|
* rfkill input events, or we will race the rfkill core input
|
|
|
|
* handler.
|
|
|
|
*
|
2011-03-18 04:18:22 +08:00
|
|
|
* tpacpi_inputdev_send_mutex works as a synchronization point
|
2009-06-02 19:01:37 +08:00
|
|
|
* for the above.
|
|
|
|
*
|
|
|
|
* We optimize to avoid numerous calls to hotkey_get_wlsw.
|
|
|
|
*/
|
|
|
|
|
|
|
|
wlsw = hotkey_get_wlsw();
|
|
|
|
|
|
|
|
/* Sync hw blocking state first if it is hw-blocked */
|
|
|
|
if (wlsw == TPACPI_RFK_RADIO_OFF)
|
|
|
|
tpacpi_rfk_update_hwblock_state(true);
|
2008-07-21 20:15:51 +08:00
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
/* Sync sw blocking state */
|
|
|
|
tpacpi_rfk_update_swstate_all();
|
|
|
|
|
|
|
|
/* Sync hw blocking state last if it is hw-unblocked */
|
|
|
|
if (wlsw == TPACPI_RFK_RADIO_ON)
|
|
|
|
tpacpi_rfk_update_hwblock_state(false);
|
|
|
|
|
|
|
|
/* Issue rfkill input event for WLSW switch */
|
|
|
|
if (!(wlsw < 0)) {
|
2008-07-21 20:15:49 +08:00
|
|
|
mutex_lock(&tpacpi_inputdev_send_mutex);
|
|
|
|
|
|
|
|
input_report_switch(tpacpi_inputdev,
|
2009-06-02 19:01:37 +08:00
|
|
|
SW_RFKILL_ALL, (wlsw > 0));
|
2008-07-21 20:15:49 +08:00
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
|
|
|
mutex_unlock(&tpacpi_inputdev_send_mutex);
|
|
|
|
}
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* this can be unconditional, as we will poll state again
|
|
|
|
* if userspace uses the notify to read data
|
|
|
|
*/
|
2008-07-21 20:15:49 +08:00
|
|
|
hotkey_radio_sw_notify_change();
|
|
|
|
}
|
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
static void hotkey_exit(void)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
2009-09-13 02:22:14 +08:00
|
|
|
mutex_lock(&hotkey_mutex);
|
2008-06-04 10:36:10 +08:00
|
|
|
hotkey_poll_stop_sync();
|
2009-09-13 02:22:14 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
2008-06-04 10:36:10 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (hotkey_dev_attributes)
|
|
|
|
delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
|
|
|
|
|
2009-09-21 01:09:23 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY,
|
2009-09-21 01:09:25 +08:00
|
|
|
"restoring original HKEY status and mask\n");
|
|
|
|
/* yes, there is a bitwise or below, we want the
|
|
|
|
* functions to be called even if one of them fail */
|
|
|
|
if (((tp_features.hotkey_mask &&
|
|
|
|
hotkey_mask_set(hotkey_orig_mask)) |
|
|
|
|
hotkey_status_set(false)) != 0)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to restore hot key mask "
|
2009-09-21 01:09:23 +08:00
|
|
|
"to BIOS defaults\n");
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
|
|
|
|
2009-09-13 02:22:18 +08:00
|
|
|
static void __init hotkey_unmap(const unsigned int scancode)
|
|
|
|
{
|
|
|
|
if (hotkey_keycode_map[scancode] != KEY_RESERVED) {
|
|
|
|
clear_bit(hotkey_keycode_map[scancode],
|
|
|
|
tpacpi_inputdev->keybit);
|
|
|
|
hotkey_keycode_map[scancode] = KEY_RESERVED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/*
|
|
|
|
* HKEY quirks:
|
|
|
|
* TPACPI_HK_Q_INIMASK: Supports FN+F3,FN+F4,FN+F12
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define TPACPI_HK_Q_INIMASK 0x0001
|
|
|
|
|
|
|
|
static const struct tpacpi_quirk tpacpi_hotkey_qtable[] __initconst = {
|
|
|
|
TPACPI_Q_IBM('I', 'H', TPACPI_HK_Q_INIMASK), /* 600E */
|
|
|
|
TPACPI_Q_IBM('I', 'N', TPACPI_HK_Q_INIMASK), /* 600E */
|
|
|
|
TPACPI_Q_IBM('I', 'D', TPACPI_HK_Q_INIMASK), /* 770, 770E, 770ED */
|
|
|
|
TPACPI_Q_IBM('I', 'W', TPACPI_HK_Q_INIMASK), /* A20m */
|
|
|
|
TPACPI_Q_IBM('I', 'V', TPACPI_HK_Q_INIMASK), /* A20p */
|
|
|
|
TPACPI_Q_IBM('1', '0', TPACPI_HK_Q_INIMASK), /* A21e, A22e */
|
|
|
|
TPACPI_Q_IBM('K', 'U', TPACPI_HK_Q_INIMASK), /* A21e */
|
|
|
|
TPACPI_Q_IBM('K', 'X', TPACPI_HK_Q_INIMASK), /* A21m, A22m */
|
|
|
|
TPACPI_Q_IBM('K', 'Y', TPACPI_HK_Q_INIMASK), /* A21p, A22p */
|
|
|
|
TPACPI_Q_IBM('1', 'B', TPACPI_HK_Q_INIMASK), /* A22e */
|
|
|
|
TPACPI_Q_IBM('1', '3', TPACPI_HK_Q_INIMASK), /* A22m */
|
|
|
|
TPACPI_Q_IBM('1', 'E', TPACPI_HK_Q_INIMASK), /* A30/p (0) */
|
|
|
|
TPACPI_Q_IBM('1', 'C', TPACPI_HK_Q_INIMASK), /* R30 */
|
|
|
|
TPACPI_Q_IBM('1', 'F', TPACPI_HK_Q_INIMASK), /* R31 */
|
|
|
|
TPACPI_Q_IBM('I', 'Y', TPACPI_HK_Q_INIMASK), /* T20 */
|
|
|
|
TPACPI_Q_IBM('K', 'Z', TPACPI_HK_Q_INIMASK), /* T21 */
|
|
|
|
TPACPI_Q_IBM('1', '6', TPACPI_HK_Q_INIMASK), /* T22 */
|
|
|
|
TPACPI_Q_IBM('I', 'Z', TPACPI_HK_Q_INIMASK), /* X20, X21 */
|
|
|
|
TPACPI_Q_IBM('1', 'D', TPACPI_HK_Q_INIMASK), /* X22, X23, X24 */
|
|
|
|
};
|
|
|
|
|
2010-09-18 08:53:41 +08:00
|
|
|
typedef u16 tpacpi_keymap_entry_t;
|
|
|
|
typedef tpacpi_keymap_entry_t tpacpi_keymap_t[TPACPI_HOTKEY_MAP_LEN];
|
2010-08-10 10:48:21 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init hotkey_init(struct ibm_init_struct *iibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
/* Requirements for changing the default keymaps:
|
|
|
|
*
|
|
|
|
* 1. Many of the keys are mapped to KEY_RESERVED for very
|
|
|
|
* good reasons. Do not change them unless you have deep
|
|
|
|
* knowledge on the IBM and Lenovo ThinkPad firmware for
|
|
|
|
* the various ThinkPad models. The driver behaves
|
|
|
|
* differently for KEY_RESERVED: such keys have their
|
|
|
|
* hot key mask *unset* in mask_recommended, and also
|
|
|
|
* in the initial hot key mask programmed into the
|
|
|
|
* firmware at driver load time, which means the firm-
|
|
|
|
* ware may react very differently if you change them to
|
|
|
|
* something else;
|
|
|
|
*
|
|
|
|
* 2. You must be subscribed to the linux-thinkpad and
|
|
|
|
* ibm-acpi-devel mailing lists, and you should read the
|
|
|
|
* list archives since 2007 if you want to change the
|
|
|
|
* keymaps. This requirement exists so that you will
|
|
|
|
* know the past history of problems with the thinkpad-
|
|
|
|
* acpi driver keymaps, and also that you will be
|
|
|
|
* listening to any bug reports;
|
|
|
|
*
|
|
|
|
* 3. Do not send thinkpad-acpi specific patches directly to
|
|
|
|
* for merging, *ever*. Send them to the linux-acpi
|
|
|
|
* mailinglist for comments. Merging is to be done only
|
|
|
|
* through acpi-test and the ACPI maintainer.
|
|
|
|
*
|
|
|
|
* If the above is too much to ask, don't change the keymap.
|
|
|
|
* Ask the thinkpad-acpi maintainer to do it, instead.
|
|
|
|
*/
|
2010-08-10 10:48:21 +08:00
|
|
|
|
|
|
|
enum keymap_index {
|
|
|
|
TPACPI_KEYMAP_IBM_GENERIC = 0,
|
|
|
|
TPACPI_KEYMAP_LENOVO_GENERIC,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const tpacpi_keymap_t tpacpi_keymaps[] __initconst = {
|
|
|
|
/* Generic keymap for IBM ThinkPads */
|
|
|
|
[TPACPI_KEYMAP_IBM_GENERIC] = {
|
2007-07-19 10:45:44 +08:00
|
|
|
/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
|
2010-08-10 10:48:21 +08:00
|
|
|
KEY_FN_F1, KEY_BATTERY, KEY_COFFEE, KEY_SLEEP,
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
|
|
|
|
KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
|
|
|
|
KEY_UNKNOWN, /* 0x0D: FN+INSERT */
|
|
|
|
KEY_UNKNOWN, /* 0x0E: FN+DELETE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* brightness: firmware always reacts to them */
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
|
|
|
|
KEY_RESERVED, /* 0x10: FN+END (brightness down) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Thinklight: firmware always react to it */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
|
|
|
|
KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Volume: firmware always react to it and reprograms
|
|
|
|
* the built-in *extra* mixer. Never map it to control
|
|
|
|
* another mixer by default. */
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x14: VOLUME UP */
|
|
|
|
KEY_RESERVED, /* 0x15: VOLUME DOWN */
|
|
|
|
KEY_RESERVED, /* 0x16: MUTE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
/* (assignments unknown, please report if found) */
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
2010-08-10 10:48:21 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
/* Generic keymap for Lenovo ThinkPads */
|
|
|
|
[TPACPI_KEYMAP_LENOVO_GENERIC] = {
|
2007-07-19 10:45:44 +08:00
|
|
|
/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
|
|
|
|
KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP,
|
2010-08-10 10:48:22 +08:00
|
|
|
KEY_WLAN, KEY_CAMERA, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
|
|
|
|
KEY_UNKNOWN, /* 0x0D: FN+INSERT */
|
|
|
|
KEY_UNKNOWN, /* 0x0E: FN+DELETE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2009-09-13 02:22:18 +08:00
|
|
|
/* These should be enabled --only-- when ACPI video
|
|
|
|
* is disabled (i.e. in "vendor" mode), and are handled
|
|
|
|
* in a special way by the init code */
|
|
|
|
KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */
|
|
|
|
KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
|
|
|
|
KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Volume: z60/z61, T60 (BIOS version?): firmware always
|
|
|
|
* react to it and reprograms the built-in *extra* mixer.
|
|
|
|
* Never map it to control another mixer by default.
|
|
|
|
*
|
|
|
|
* T60?, T61, R60?, R61: firmware and EC tries to send
|
|
|
|
* these over the regular keyboard, so these are no-ops,
|
|
|
|
* but there are still weird bugs re. MUTE, so do not
|
|
|
|
* change unless you get test reports from all Lenovo
|
|
|
|
* models. May cause the BIOS to interfere with the
|
|
|
|
* HDA mixer.
|
|
|
|
*/
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x14: VOLUME UP */
|
|
|
|
KEY_RESERVED, /* 0x15: VOLUME DOWN */
|
|
|
|
KEY_RESERVED, /* 0x16: MUTE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2011-05-25 03:16:43 +08:00
|
|
|
/* (assignments unknown, please report if found) */
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The mic mute button only sends 0x1a. It does not
|
|
|
|
* automatically mute the mic or change the mute light.
|
|
|
|
*/
|
|
|
|
KEY_MICMUTE, /* 0x1a: Mic mute (since ?400 or so) */
|
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
/* (assignments unknown, please report if found) */
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
2011-05-25 03:16:43 +08:00
|
|
|
KEY_UNKNOWN,
|
2010-08-10 10:48:21 +08:00
|
|
|
},
|
2007-07-19 10:45:44 +08:00
|
|
|
};
|
|
|
|
|
2010-08-10 10:48:21 +08:00
|
|
|
static const struct tpacpi_quirk tpacpi_keymap_qtable[] __initconst = {
|
|
|
|
/* Generic maps (fallback) */
|
|
|
|
{
|
|
|
|
.vendor = PCI_VENDOR_ID_IBM,
|
|
|
|
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = TPACPI_KEYMAP_IBM_GENERIC,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.vendor = PCI_VENDOR_ID_LENOVO,
|
|
|
|
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = TPACPI_KEYMAP_LENOVO_GENERIC,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
#define TPACPI_HOTKEY_MAP_SIZE sizeof(tpacpi_keymap_t)
|
2010-09-18 08:53:41 +08:00
|
|
|
#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(tpacpi_keymap_entry_t)
|
2007-07-19 10:45:44 +08:00
|
|
|
|
2007-07-19 10:45:35 +08:00
|
|
|
int res, i;
|
2007-07-19 10:45:31 +08:00
|
|
|
int status;
|
2007-09-23 22:39:03 +08:00
|
|
|
int hkeyv;
|
2009-12-16 07:51:06 +08:00
|
|
|
bool radiosw_state = false;
|
|
|
|
bool tabletsw_state = false;
|
2007-04-21 22:08:39 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
unsigned long quirks;
|
2010-08-10 10:48:21 +08:00
|
|
|
unsigned long keymap_id;
|
2009-09-21 01:09:25 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"initializing hotkey subdriver\n");
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-07-19 10:45:35 +08:00
|
|
|
BUG_ON(!tpacpi_inputdev);
|
2008-01-08 23:02:41 +08:00
|
|
|
BUG_ON(tpacpi_inputdev->open != NULL ||
|
|
|
|
tpacpi_inputdev->close != NULL);
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(hkey);
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_init(&hotkey_mutex);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
mutex_init(&hotkey_thread_mutex);
|
|
|
|
mutex_init(&hotkey_thread_data_mutex);
|
|
|
|
#endif
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* hotkey not supported on 570 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.hotkey = hkey_handle != NULL;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"hotkeys are %s\n",
|
2007-04-21 22:08:36 +08:00
|
|
|
str_supported(tp_features.hotkey));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
if (!tp_features.hotkey)
|
|
|
|
return 1;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable,
|
|
|
|
ARRAY_SIZE(tpacpi_hotkey_qtable));
|
|
|
|
|
2008-10-19 01:23:55 +08:00
|
|
|
tpacpi_disable_brightness_delay();
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* MUST have enough space for all attributes to be added to
|
|
|
|
* hotkey_dev_attributes */
|
|
|
|
hotkey_dev_attributes = create_attr_set(
|
|
|
|
ARRAY_SIZE(hotkey_attributes) + 2,
|
|
|
|
NULL);
|
2008-06-04 10:36:10 +08:00
|
|
|
if (!hotkey_dev_attributes)
|
|
|
|
return -ENOMEM;
|
|
|
|
res = add_many_to_attr_set(hotkey_dev_attributes,
|
|
|
|
hotkey_attributes,
|
|
|
|
ARRAY_SIZE(hotkey_attributes));
|
|
|
|
if (res)
|
|
|
|
goto err_exit;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* mask not supported on 600e/x, 770e, 770x, A21e, A2xm/p,
|
2008-06-04 10:36:10 +08:00
|
|
|
A30, R30, R31, T20-22, X20-21, X22-24. Detected by checking
|
|
|
|
for HKEY interface version 0x100 */
|
|
|
|
if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
|
|
|
|
if ((hkeyv >> 8) != 1) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unknown version of the HKEY interface: 0x%x\n",
|
|
|
|
hkeyv);
|
|
|
|
pr_err("please report this to %s\n", TPACPI_MAIL);
|
2008-06-04 10:36:10 +08:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* MHKV 0x100 in A31, R40, R40e,
|
|
|
|
* T4x, X31, and later
|
|
|
|
*/
|
2009-04-04 12:25:51 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"firmware HKEY interface version: 0x%x\n",
|
|
|
|
hkeyv);
|
2009-09-21 01:09:25 +08:00
|
|
|
|
|
|
|
/* Paranoia check AND init hotkey_all_mask */
|
|
|
|
if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
|
|
|
|
"MHKA", "qd")) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("missing MHKA handler, "
|
2009-09-21 01:09:25 +08:00
|
|
|
"please report this to %s\n",
|
|
|
|
TPACPI_MAIL);
|
|
|
|
/* Fallback: pre-init for FN+F3,F4,F12 */
|
|
|
|
hotkey_all_mask = 0x080cU;
|
|
|
|
} else {
|
|
|
|
tp_features.hotkey_mask = 1;
|
|
|
|
}
|
2007-09-23 22:39:03 +08:00
|
|
|
}
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"hotkey masks are %s\n",
|
2008-06-04 10:36:10 +08:00
|
|
|
str_supported(tp_features.hotkey_mask));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* Init hotkey_all_mask if not initialized yet */
|
|
|
|
if (!tp_features.hotkey_mask && !hotkey_all_mask &&
|
|
|
|
(quirks & TPACPI_HK_Q_INIMASK))
|
|
|
|
hotkey_all_mask = 0x080cU; /* FN+F12, FN+F4, FN+F3 */
|
2007-07-19 10:45:30 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
/* Init hotkey_acpi_mask and hotkey_orig_mask */
|
2008-06-04 10:36:10 +08:00
|
|
|
if (tp_features.hotkey_mask) {
|
2009-09-21 01:09:25 +08:00
|
|
|
/* hotkey_source_mask *must* be zero for
|
|
|
|
* the first hotkey_mask_get to return hotkey_orig_mask */
|
2008-06-04 10:36:10 +08:00
|
|
|
res = hotkey_mask_get();
|
|
|
|
if (res)
|
|
|
|
goto err_exit;
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
hotkey_orig_mask = hotkey_acpi_mask;
|
|
|
|
} else {
|
|
|
|
hotkey_orig_mask = hotkey_all_mask;
|
|
|
|
hotkey_acpi_mask = hotkey_all_mask;
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
2007-07-19 10:45:31 +08:00
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_wlswemul) {
|
|
|
|
tp_features.hotkey_wlsw = 1;
|
2009-12-16 07:51:06 +08:00
|
|
|
radiosw_state = !!tpacpi_wlsw_emulstate;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("radio switch emulation enabled\n");
|
2009-01-11 13:01:00 +08:00
|
|
|
} else
|
|
|
|
#endif
|
2008-06-04 10:36:10 +08:00
|
|
|
/* Not all thinkpads have a hardware radio switch */
|
|
|
|
if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
|
|
|
|
tp_features.hotkey_wlsw = 1;
|
2009-12-16 07:51:06 +08:00
|
|
|
radiosw_state = !!status;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("radio switch found; radios are %s\n",
|
2008-06-04 10:36:10 +08:00
|
|
|
enabled(status, 0));
|
2008-07-21 20:15:49 +08:00
|
|
|
}
|
|
|
|
if (tp_features.hotkey_wlsw)
|
2008-06-04 10:36:10 +08:00
|
|
|
res = add_to_attr_set(hotkey_dev_attributes,
|
|
|
|
&dev_attr_hotkey_radio_sw.attr);
|
2007-07-19 10:45:31 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
/* For X41t, X60t, X61t Tablets... */
|
|
|
|
if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
|
|
|
|
tp_features.hotkey_tablet = 1;
|
2009-12-16 07:51:06 +08:00
|
|
|
tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("possible tablet mode switch found; "
|
2008-06-04 10:36:10 +08:00
|
|
|
"ThinkPad in %s mode\n",
|
2009-12-16 07:51:06 +08:00
|
|
|
(tabletsw_state) ? "tablet" : "laptop");
|
2008-06-04 10:36:10 +08:00
|
|
|
res = add_to_attr_set(hotkey_dev_attributes,
|
|
|
|
&dev_attr_hotkey_tablet_mode.attr);
|
|
|
|
}
|
2008-02-16 12:17:58 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
if (!res)
|
|
|
|
res = register_attr_set_with_sysfs(
|
|
|
|
hotkey_dev_attributes,
|
|
|
|
&tpacpi_pdev->dev.kobj);
|
|
|
|
if (res)
|
|
|
|
goto err_exit;
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
/* Set up key map */
|
|
|
|
hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!hotkey_keycode_map) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to allocate memory for key map\n");
|
2008-06-04 10:36:10 +08:00
|
|
|
res = -ENOMEM;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
2007-07-19 10:45:44 +08:00
|
|
|
|
2010-08-10 10:48:21 +08:00
|
|
|
keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
|
|
|
|
ARRAY_SIZE(tpacpi_keymap_qtable));
|
|
|
|
BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps));
|
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"using keymap number %lu\n", keymap_id);
|
|
|
|
|
|
|
|
memcpy(hotkey_keycode_map, &tpacpi_keymaps[keymap_id],
|
|
|
|
TPACPI_HOTKEY_MAP_SIZE);
|
2007-07-19 10:45:44 +08:00
|
|
|
|
2009-12-09 09:36:29 +08:00
|
|
|
input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
|
2008-06-04 10:36:10 +08:00
|
|
|
tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
|
|
|
|
tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
|
|
|
|
tpacpi_inputdev->keycode = hotkey_keycode_map;
|
|
|
|
for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
|
|
|
|
if (hotkey_keycode_map[i] != KEY_RESERVED) {
|
2009-12-09 09:36:29 +08:00
|
|
|
input_set_capability(tpacpi_inputdev, EV_KEY,
|
|
|
|
hotkey_keycode_map[i]);
|
2008-06-04 10:36:10 +08:00
|
|
|
} else {
|
|
|
|
if (i < sizeof(hotkey_reserved_mask)*8)
|
|
|
|
hotkey_reserved_mask |= 1 << i;
|
2007-07-19 10:45:35 +08:00
|
|
|
}
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
if (tp_features.hotkey_wlsw) {
|
2009-12-09 09:36:29 +08:00
|
|
|
input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
|
2009-12-16 07:51:06 +08:00
|
|
|
input_report_switch(tpacpi_inputdev,
|
|
|
|
SW_RFKILL_ALL, radiosw_state);
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
|
|
|
if (tp_features.hotkey_tablet) {
|
2009-12-09 09:36:29 +08:00
|
|
|
input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
|
2009-12-16 07:51:06 +08:00
|
|
|
input_report_switch(tpacpi_inputdev,
|
|
|
|
SW_TABLET_MODE, tabletsw_state);
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
2007-07-19 10:45:38 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
/* Do not issue duplicate brightness change events to
|
2010-05-17 06:45:33 +08:00
|
|
|
* userspace. tpacpi_detect_brightness_capabilities() must have
|
|
|
|
* been called before this point */
|
2012-04-24 16:40:52 +08:00
|
|
|
if (acpi_video_backlight_support()) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("This ThinkPad has standard ACPI backlight "
|
|
|
|
"brightness control, supported by the ACPI "
|
|
|
|
"video driver\n");
|
|
|
|
pr_notice("Disabling thinkpad-acpi brightness events "
|
|
|
|
"by default...\n");
|
2008-06-04 10:36:10 +08:00
|
|
|
|
2009-09-13 02:22:18 +08:00
|
|
|
/* Disable brightness up/down on Lenovo thinkpads when
|
|
|
|
* ACPI is handling them, otherwise it is plain impossible
|
|
|
|
* for userspace to do something even remotely sane */
|
2008-06-04 10:36:10 +08:00
|
|
|
hotkey_reserved_mask |=
|
|
|
|
(1 << TP_ACPI_HOTKEYSCAN_FNHOME)
|
|
|
|
| (1 << TP_ACPI_HOTKEYSCAN_FNEND);
|
2009-09-13 02:22:18 +08:00
|
|
|
hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME);
|
|
|
|
hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND);
|
2008-06-04 10:36:10 +08:00
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2009-09-13 02:22:17 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
2009-09-21 01:09:25 +08:00
|
|
|
hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
|
|
|
|
& ~hotkey_all_mask
|
|
|
|
& ~hotkey_reserved_mask;
|
2009-09-13 02:22:17 +08:00
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"hotkey source mask 0x%08x, polling freq %u\n",
|
|
|
|
hotkey_source_mask, hotkey_poll_freq);
|
|
|
|
#endif
|
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"enabling firmware HKEY event interface...\n");
|
2009-04-04 12:25:48 +08:00
|
|
|
res = hotkey_status_set(true);
|
2008-06-04 10:36:10 +08:00
|
|
|
if (res) {
|
|
|
|
hotkey_exit();
|
|
|
|
return res;
|
|
|
|
}
|
2009-09-21 01:09:25 +08:00
|
|
|
res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
|
|
|
|
| hotkey_driver_mask)
|
|
|
|
& ~hotkey_source_mask);
|
2008-06-04 10:36:10 +08:00
|
|
|
if (res < 0 && res != -ENXIO) {
|
|
|
|
hotkey_exit();
|
|
|
|
return res;
|
|
|
|
}
|
2009-09-21 01:09:25 +08:00
|
|
|
hotkey_user_mask = (hotkey_acpi_mask | hotkey_source_mask)
|
|
|
|
& ~hotkey_reserved_mask;
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"initial masks: user=0x%08x, fw=0x%08x, poll=0x%08x\n",
|
|
|
|
hotkey_user_mask, hotkey_acpi_mask, hotkey_source_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2009-04-04 12:25:51 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
|
|
|
"legacy ibm/hotkey event reporting over procfs %s\n",
|
2008-06-04 10:36:10 +08:00
|
|
|
(hotkey_report_mode < 2) ?
|
|
|
|
"enabled" : "disabled");
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
tpacpi_inputdev->open = &hotkey_inputdev_open;
|
|
|
|
tpacpi_inputdev->close = &hotkey_inputdev_close;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup_safe(true);
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
return 0;
|
2008-01-08 23:02:41 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
err_exit:
|
|
|
|
delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
|
|
|
|
hotkey_dev_attributes = NULL;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
return (res < 0)? res : 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2009-01-11 13:01:05 +08:00
|
|
|
static bool hotkey_notify_hotkey(const u32 hkey,
|
|
|
|
bool *send_acpi_ev,
|
|
|
|
bool *ignore_acpi_ev)
|
|
|
|
{
|
|
|
|
/* 0x1000-0x1FFF: key presses */
|
|
|
|
unsigned int scancode = hkey & 0xfff;
|
|
|
|
*send_acpi_ev = true;
|
|
|
|
*ignore_acpi_ev = false;
|
|
|
|
|
2010-08-10 10:48:20 +08:00
|
|
|
/* HKEY event 0x1001 is scancode 0x00 */
|
|
|
|
if (scancode > 0 && scancode <= TPACPI_HOTKEY_MAP_LEN) {
|
2009-01-11 13:01:05 +08:00
|
|
|
scancode--;
|
|
|
|
if (!(hotkey_source_mask & (1 << scancode))) {
|
2009-09-21 01:09:25 +08:00
|
|
|
tpacpi_input_send_key_masked(scancode);
|
2009-01-11 13:01:05 +08:00
|
|
|
*send_acpi_ev = false;
|
|
|
|
} else {
|
|
|
|
*ignore_acpi_ev = true;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool hotkey_notify_wakeup(const u32 hkey,
|
|
|
|
bool *send_acpi_ev,
|
|
|
|
bool *ignore_acpi_ev)
|
|
|
|
{
|
|
|
|
/* 0x2000-0x2FFF: Wakeup reason */
|
|
|
|
*send_acpi_ev = true;
|
|
|
|
*ignore_acpi_ev = false;
|
|
|
|
|
|
|
|
switch (hkey) {
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_WKUP_S3_UNDOCK: /* suspend, undock */
|
|
|
|
case TP_HKEY_EV_WKUP_S4_UNDOCK: /* hibernation, undock */
|
2009-01-11 13:01:05 +08:00
|
|
|
hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK;
|
|
|
|
*ignore_acpi_ev = true;
|
|
|
|
break;
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_WKUP_S3_BAYEJ: /* suspend, bay eject */
|
|
|
|
case TP_HKEY_EV_WKUP_S4_BAYEJ: /* hibernation, bay eject */
|
2009-01-11 13:01:05 +08:00
|
|
|
hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
|
|
|
|
*ignore_acpi_ev = true;
|
|
|
|
break;
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */
|
|
|
|
case TP_HKEY_EV_WKUP_S4_BATLOW: /* Battery on critical low level/S4 */
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_alert("EMERGENCY WAKEUP: battery almost empty\n");
|
2009-01-11 13:01:06 +08:00
|
|
|
/* how to auto-heal: */
|
|
|
|
/* 2313: woke up from S3, go to S4/S5 */
|
|
|
|
/* 2413: woke up from S4, go to S5 */
|
|
|
|
break;
|
|
|
|
|
2009-01-11 13:01:05 +08:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("woke up due to a hot-unplug request...\n");
|
2009-01-11 13:01:05 +08:00
|
|
|
hotkey_wakeup_reason_notify_change();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-06-06 03:22:35 +08:00
|
|
|
static bool hotkey_notify_dockevent(const u32 hkey,
|
|
|
|
bool *send_acpi_ev,
|
|
|
|
bool *ignore_acpi_ev)
|
|
|
|
{
|
|
|
|
/* 0x4000-0x4FFF: dock-related events */
|
|
|
|
*send_acpi_ev = true;
|
|
|
|
*ignore_acpi_ev = false;
|
|
|
|
|
|
|
|
switch (hkey) {
|
|
|
|
case TP_HKEY_EV_UNDOCK_ACK:
|
|
|
|
/* ACPI undock operation completed after wakeup */
|
|
|
|
hotkey_autosleep_ack = 1;
|
|
|
|
pr_info("undocked\n");
|
|
|
|
hotkey_wakeup_hotunplug_complete_notify_change();
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case TP_HKEY_EV_HOTPLUG_DOCK: /* docked to port replicator */
|
|
|
|
pr_info("docked into hotplug port replicator\n");
|
|
|
|
return true;
|
|
|
|
case TP_HKEY_EV_HOTPLUG_UNDOCK: /* undocked from port replicator */
|
|
|
|
pr_info("undocked from hotplug port replicator\n");
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-11 13:01:05 +08:00
|
|
|
static bool hotkey_notify_usrevent(const u32 hkey,
|
|
|
|
bool *send_acpi_ev,
|
|
|
|
bool *ignore_acpi_ev)
|
|
|
|
{
|
|
|
|
/* 0x5000-0x5FFF: human interface helpers */
|
|
|
|
*send_acpi_ev = true;
|
|
|
|
*ignore_acpi_ev = false;
|
|
|
|
|
|
|
|
switch (hkey) {
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_PEN_INSERTED: /* X61t: tablet pen inserted into bay */
|
|
|
|
case TP_HKEY_EV_PEN_REMOVED: /* X61t: tablet pen removed from bay */
|
2009-01-11 13:01:05 +08:00
|
|
|
return true;
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_TABLET_TABLET: /* X41t-X61t: tablet mode */
|
|
|
|
case TP_HKEY_EV_TABLET_NOTEBOOK: /* X41t-X61t: normal mode */
|
2009-01-11 13:01:05 +08:00
|
|
|
tpacpi_input_send_tabletsw();
|
|
|
|
hotkey_tablet_mode_notify_change();
|
|
|
|
*send_acpi_ev = false;
|
|
|
|
return true;
|
|
|
|
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_LID_CLOSE: /* Lid closed */
|
|
|
|
case TP_HKEY_EV_LID_OPEN: /* Lid opened */
|
|
|
|
case TP_HKEY_EV_BRGHT_CHANGED: /* brightness changed */
|
2009-09-21 01:09:24 +08:00
|
|
|
/* do not propagate these events */
|
2009-01-11 13:01:05 +08:00
|
|
|
*ignore_acpi_ev = true;
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-09 09:36:28 +08:00
|
|
|
static void thermal_dump_all_sensors(void);
|
|
|
|
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
static bool hotkey_notify_6xxx(const u32 hkey,
|
2009-01-11 13:01:06 +08:00
|
|
|
bool *send_acpi_ev,
|
|
|
|
bool *ignore_acpi_ev)
|
|
|
|
{
|
2009-12-09 09:36:28 +08:00
|
|
|
bool known = true;
|
|
|
|
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
/* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */
|
2009-01-11 13:01:06 +08:00
|
|
|
*send_acpi_ev = true;
|
|
|
|
*ignore_acpi_ev = false;
|
|
|
|
|
|
|
|
switch (hkey) {
|
2009-12-09 09:36:28 +08:00
|
|
|
case TP_HKEY_EV_THM_TABLE_CHANGED:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("EC reports that Thermal Table has changed\n");
|
2009-12-09 09:36:28 +08:00
|
|
|
/* recommended action: do nothing, we don't have
|
|
|
|
* Lenovo ATM information */
|
|
|
|
return true;
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_ALARM_BAT_HOT:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_crit("THERMAL ALARM: battery is too hot!\n");
|
2009-01-11 13:01:06 +08:00
|
|
|
/* recommended action: warn user through gui */
|
2009-12-09 09:36:28 +08:00
|
|
|
break;
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_ALARM_BAT_XHOT:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n");
|
2009-01-11 13:01:06 +08:00
|
|
|
/* recommended action: immediate sleep/hibernate */
|
2009-12-09 09:36:28 +08:00
|
|
|
break;
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_ALARM_SENSOR_HOT:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_crit("THERMAL ALARM: "
|
2009-01-11 13:01:06 +08:00
|
|
|
"a sensor reports something is too hot!\n");
|
|
|
|
/* recommended action: warn user through gui, that */
|
|
|
|
/* some internal component is too hot */
|
2009-12-09 09:36:28 +08:00
|
|
|
break;
|
2009-09-21 01:09:27 +08:00
|
|
|
case TP_HKEY_EV_ALARM_SENSOR_XHOT:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_alert("THERMAL EMERGENCY: "
|
|
|
|
"a sensor reports something is extremely hot!\n");
|
2009-01-11 13:01:06 +08:00
|
|
|
/* recommended action: immediate sleep/hibernate */
|
2009-12-09 09:36:28 +08:00
|
|
|
break;
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
|
|
|
|
case TP_HKEY_EV_KEY_NUMLOCK:
|
|
|
|
case TP_HKEY_EV_KEY_FN:
|
|
|
|
/* key press events, we just ignore them as long as the EC
|
|
|
|
* is still reporting them in the normal keyboard stream */
|
|
|
|
*send_acpi_ev = false;
|
|
|
|
*ignore_acpi_ev = true;
|
|
|
|
return true;
|
|
|
|
|
2009-01-11 13:01:06 +08:00
|
|
|
default:
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
pr_warn("unknown possible thermal alarm or keyboard event received\n");
|
2009-12-09 09:36:28 +08:00
|
|
|
known = false;
|
2009-01-11 13:01:06 +08:00
|
|
|
}
|
2009-12-09 09:36:28 +08:00
|
|
|
|
|
|
|
thermal_dump_all_sensors();
|
|
|
|
|
|
|
|
return known;
|
2009-01-11 13:01:06 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void hotkey_notify(struct ibm_struct *ibm, u32 event)
|
|
|
|
{
|
2007-07-19 10:45:35 +08:00
|
|
|
u32 hkey;
|
2009-01-11 13:01:05 +08:00
|
|
|
bool send_acpi_ev;
|
|
|
|
bool ignore_acpi_ev;
|
|
|
|
bool known_ev;
|
2007-09-23 22:39:04 +08:00
|
|
|
|
|
|
|
if (event != 0x80) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unknown HKEY notification event %d\n", event);
|
2007-09-23 22:39:04 +08:00
|
|
|
/* forward it to userspace, maybe it knows how to handle it */
|
2008-01-08 23:02:49 +08:00
|
|
|
acpi_bus_generate_netlink_event(
|
|
|
|
ibm->acpi->device->pnp.device_class,
|
2009-01-11 13:00:59 +08:00
|
|
|
dev_name(&ibm->acpi->device->dev),
|
2008-01-08 23:02:49 +08:00
|
|
|
event, 0);
|
2007-09-23 22:39:04 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to retrieve HKEY event\n");
|
2007-09-23 22:39:04 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hkey == 0) {
|
|
|
|
/* queue empty */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-01-11 13:01:05 +08:00
|
|
|
send_acpi_ev = true;
|
|
|
|
ignore_acpi_ev = false;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
switch (hkey >> 12) {
|
|
|
|
case 1:
|
|
|
|
/* 0x1000-0x1FFF: key presses */
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev,
|
|
|
|
&ignore_acpi_ev);
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
break;
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
case 2:
|
2009-01-11 13:01:05 +08:00
|
|
|
/* 0x2000-0x2FFF: Wakeup reason */
|
|
|
|
known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev,
|
|
|
|
&ignore_acpi_ev);
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
break;
|
|
|
|
case 3:
|
2009-01-11 13:01:05 +08:00
|
|
|
/* 0x3000-0x3FFF: bay-related wakeups */
|
2010-02-26 08:28:56 +08:00
|
|
|
switch (hkey) {
|
|
|
|
case TP_HKEY_EV_BAYEJ_ACK:
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
hotkey_autosleep_ack = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("bay ejected\n");
|
2008-01-08 23:02:55 +08:00
|
|
|
hotkey_wakeup_hotunplug_complete_notify_change();
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = true;
|
2010-02-26 08:28:56 +08:00
|
|
|
break;
|
|
|
|
case TP_HKEY_EV_OPTDRV_EJ:
|
|
|
|
/* FIXME: kick libata if SATA link offline */
|
|
|
|
known_ev = true;
|
|
|
|
break;
|
|
|
|
default:
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = false;
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
2011-06-06 03:22:35 +08:00
|
|
|
/* 0x4000-0x4FFF: dock-related events */
|
|
|
|
known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev,
|
|
|
|
&ignore_acpi_ev);
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
break;
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
case 5:
|
2008-01-08 23:02:53 +08:00
|
|
|
/* 0x5000-0x5FFF: human interface helpers */
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev,
|
|
|
|
&ignore_acpi_ev);
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
break;
|
2009-01-11 13:01:06 +08:00
|
|
|
case 6:
|
thinkpad-acpi: handle some new HKEY 0x60xx events
Handle some user interface events from the newer Lenovo models. We are likely
to do something smart with these events in the future, for now, hide the ones
we are already certain about from the user and userspace both.
* Events 0x6000 and 0x6005 are key-related. 0x6005 is not properly identified
yet. Ignore these events, and do not report them.
* Event 0x6040 has not been properly identified yet, and we don't know if it
is important (looks like it isn't, but still...). Keep reporting it.
* Change the message the driver outputs on unknown 0x6xxx events, as all
recent events are not related to thermal alarms. Degrade log level from
ALERT to WARNING.
Thanks to all users who reported these events or asked about them in a number
of mailing lists. Your help is highly appreciated, even if I did took a lot of
time to act on them. For that I apologise.
I will list those that identified the reasons for the events as "reported-by",
and I apologise in advance if I leave anyone out: it was not done on purpose, I
made the mistake of not properly tagging all event report emails separately,
and might have missed some.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Reported-by: Markus Malkusch <markus@malkusch.de>
Reported-by: Peter Giles <g1l3sp@gmail.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2011-06-06 03:22:34 +08:00
|
|
|
/* 0x6000-0x6FFF: thermal alarms/notices and
|
|
|
|
* keyboard events */
|
|
|
|
known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev,
|
2009-01-11 13:01:06 +08:00
|
|
|
&ignore_acpi_ev);
|
|
|
|
break;
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
case 7:
|
|
|
|
/* 0x7000-0x7FFF: misc */
|
2009-09-21 01:09:27 +08:00
|
|
|
if (tp_features.hotkey_wlsw &&
|
|
|
|
hkey == TP_HKEY_EV_RFKILL_CHANGED) {
|
2008-07-21 20:15:49 +08:00
|
|
|
tpacpi_send_radiosw_update();
|
2008-01-08 23:02:51 +08:00
|
|
|
send_acpi_ev = 0;
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = true;
|
2007-07-19 10:45:35 +08:00
|
|
|
break;
|
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* fallthrough to default */
|
|
|
|
default:
|
2009-01-11 13:01:05 +08:00
|
|
|
known_ev = false;
|
2008-01-08 23:02:51 +08:00
|
|
|
}
|
2009-01-11 13:01:05 +08:00
|
|
|
if (!known_ev) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("unhandled HKEY event 0x%04x\n", hkey);
|
|
|
|
pr_notice("please report the conditions when this "
|
|
|
|
"event happened to %s\n", TPACPI_MAIL);
|
2007-07-19 10:45:35 +08:00
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2007-09-23 22:39:04 +08:00
|
|
|
/* Legacy events */
|
2008-01-08 23:02:49 +08:00
|
|
|
if (!ignore_acpi_ev &&
|
|
|
|
(send_acpi_ev || hotkey_report_mode < 2)) {
|
|
|
|
acpi_bus_generate_proc_event(ibm->acpi->device,
|
|
|
|
event, hkey);
|
2007-09-23 22:39:05 +08:00
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2007-09-23 22:39:04 +08:00
|
|
|
/* netlink events */
|
2007-09-23 22:39:05 +08:00
|
|
|
if (!ignore_acpi_ev && send_acpi_ev) {
|
2008-01-08 23:02:49 +08:00
|
|
|
acpi_bus_generate_netlink_event(
|
|
|
|
ibm->acpi->device->pnp.device_class,
|
2009-01-11 13:00:59 +08:00
|
|
|
dev_name(&ibm->acpi->device->dev),
|
2008-01-08 23:02:49 +08:00
|
|
|
event, hkey);
|
2007-09-23 22:39:04 +08:00
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 05:18:44 +08:00
|
|
|
static void hotkey_suspend(void)
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
{
|
|
|
|
/* Do these on suspend, we get the events on early resume! */
|
|
|
|
hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
|
|
|
|
hotkey_autosleep_ack = 0;
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:38 +08:00
|
|
|
static void hotkey_resume(void)
|
|
|
|
{
|
2008-10-19 01:23:55 +08:00
|
|
|
tpacpi_disable_brightness_delay();
|
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (hotkey_status_set(true) < 0 ||
|
|
|
|
hotkey_mask_set(hotkey_acpi_mask) < 0)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("error while attempting to reset the event "
|
2009-09-21 01:09:25 +08:00
|
|
|
"firmware interface\n");
|
|
|
|
|
2008-07-21 20:15:49 +08:00
|
|
|
tpacpi_send_radiosw_update();
|
2008-02-16 12:17:58 +08:00
|
|
|
hotkey_tablet_mode_notify_change();
|
2008-01-08 23:02:55 +08:00
|
|
|
hotkey_wakeup_reason_notify_change();
|
|
|
|
hotkey_wakeup_hotunplug_complete_notify_change();
|
2009-09-13 02:22:14 +08:00
|
|
|
hotkey_poll_setup_safe(false);
|
2007-07-19 10:45:38 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2009-12-16 07:51:12 +08:00
|
|
|
static int hotkey_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
int res, status;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.hotkey) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&hotkey_mutex))
|
2007-10-31 03:46:24 +08:00
|
|
|
return -ERESTARTSYS;
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_get(&status);
|
|
|
|
if (!res)
|
|
|
|
res = hotkey_mask_get();
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
2007-04-21 22:08:39 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
|
2009-09-21 01:09:25 +08:00
|
|
|
if (hotkey_all_mask) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
|
|
|
|
seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "mask:\t\tnot supported\n");
|
|
|
|
seq_printf(m, "commands:\tenable, disable, reset\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-04-14 10:44:10 +08:00
|
|
|
static void hotkey_enabledisable_warn(bool enable)
|
2009-04-04 12:25:48 +08:00
|
|
|
{
|
|
|
|
tpacpi_log_usertask("procfs hotkey enable/disable");
|
2009-04-14 10:44:10 +08:00
|
|
|
if (!WARN((tpacpi_lifecycle == TPACPI_LIFE_RUNNING || !enable),
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_fmt("hotkey enable/disable functionality has been "
|
|
|
|
"removed from the driver. "
|
|
|
|
"Hotkeys are always enabled.\n")))
|
|
|
|
pr_err("Please remove the hotkey=enable module "
|
|
|
|
"parameter, it is deprecated. "
|
|
|
|
"Hotkeys are always enabled.\n");
|
2009-04-04 12:25:48 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int hotkey_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2009-04-04 12:25:48 +08:00
|
|
|
int res;
|
2007-07-19 10:45:29 +08:00
|
|
|
u32 mask;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *cmd;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.hotkey)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&hotkey_mutex))
|
2007-10-31 03:46:24 +08:00
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
mask = hotkey_user_mask;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
res = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "enable") == 0) {
|
2009-04-14 10:44:10 +08:00
|
|
|
hotkey_enabledisable_warn(1);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "disable") == 0) {
|
2009-04-14 10:44:10 +08:00
|
|
|
hotkey_enabledisable_warn(0);
|
2009-04-04 12:25:48 +08:00
|
|
|
res = -EPERM;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "reset") == 0) {
|
2009-09-13 02:22:16 +08:00
|
|
|
mask = (hotkey_all_mask | hotkey_source_mask)
|
|
|
|
& ~hotkey_reserved_mask;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (sscanf(cmd, "0x%x", &mask) == 1) {
|
|
|
|
/* mask set */
|
|
|
|
} else if (sscanf(cmd, "%x", &mask) == 1) {
|
|
|
|
/* mask set */
|
2007-04-24 22:48:15 +08:00
|
|
|
} else {
|
|
|
|
res = -EINVAL;
|
|
|
|
goto errexit;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2009-04-04 12:25:51 +08:00
|
|
|
|
2009-09-21 01:09:25 +08:00
|
|
|
if (!res) {
|
2009-04-04 12:25:51 +08:00
|
|
|
tpacpi_disclose_usertask("procfs hotkey",
|
|
|
|
"set mask to 0x%08x\n", mask);
|
2009-09-21 01:09:25 +08:00
|
|
|
res = hotkey_user_mask_set(mask);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
errexit:
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
return res;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-23 20:44:41 +08:00
|
|
|
static const struct acpi_device_id ibm_htk_device_ids[] = {
|
2011-05-09 06:04:29 +08:00
|
|
|
{TPACPI_ACPI_IBM_HKEY_HID, 0},
|
|
|
|
{TPACPI_ACPI_LENOVO_HKEY_HID, 0},
|
2007-07-23 20:44:41 +08:00
|
|
|
{"", 0},
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
|
2007-07-23 20:44:41 +08:00
|
|
|
.hid = ibm_htk_device_ids,
|
2007-04-21 22:08:37 +08:00
|
|
|
.notify = hotkey_notify,
|
|
|
|
.handle = &hkey_handle,
|
|
|
|
.type = ACPI_DEVICE_NOTIFY,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct hotkey_driver_data = {
|
|
|
|
.name = "hotkey",
|
|
|
|
.read = hotkey_read,
|
|
|
|
.write = hotkey_write,
|
|
|
|
.exit = hotkey_exit,
|
2007-07-19 10:45:38 +08:00
|
|
|
.resume = hotkey_resume,
|
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 <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:52 +08:00
|
|
|
.suspend = hotkey_suspend,
|
2007-04-21 22:08:37 +08:00
|
|
|
.acpi = &ibm_hotkey_acpidriver,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Bluetooth subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
enum {
|
|
|
|
/* ACPI GBDC/SBDC bits */
|
|
|
|
TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */
|
|
|
|
TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */
|
2009-01-11 13:01:01 +08:00
|
|
|
TP_ACPI_BLUETOOTH_RESUMECTRL = 0x04, /* Bluetooth state at resume:
|
thinkpad-acpi: fix bluetooth/wwan resume
Studying the DSDTs of various thinkpads, it looks like bit 3 of the
argument to SBDC and SWAN is not "set radio to last state on resume".
Rather, it seems to be "if this bit is set, enable radio on resume,
otherwise disable it on resume".
So, the proper way to prepare the radios for S3 suspend is: disable
radio and clear bit 3 on the SBDC/SWAN call to to resume with radio
disabled, and enable radio and set bit 3 on the SBDC/SWAN call to
resume with the radio enabled.
Also, for persistent devices, the rfkill core does not restore state,
so we really need to get the firmware to do the right thing.
We don't sync the radio state on suspend, instead we trust the BIOS to
not do anything weird if we never touched the radio state since boot.
Time will tell if that's a wise way of doing things...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: stable@kernel.org
2010-02-26 09:22:07 +08:00
|
|
|
0 = disable, 1 = enable */
|
2009-01-11 13:01:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
/* ACPI \BLTH commands */
|
|
|
|
TP_ACPI_BLTH_GET_ULTRAPORT_ID = 0x00, /* Get Ultraport BT ID */
|
|
|
|
TP_ACPI_BLTH_GET_PWR_ON_RESUME = 0x01, /* Get power-on-resume state */
|
|
|
|
TP_ACPI_BLTH_PWR_ON_ON_RESUME = 0x02, /* Resume powered on */
|
|
|
|
TP_ACPI_BLTH_PWR_OFF_ON_RESUME = 0x03, /* Resume powered off */
|
|
|
|
TP_ACPI_BLTH_SAVE_STATE = 0x05, /* Save state for S4/S5 */
|
2008-01-08 23:02:44 +08:00
|
|
|
};
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
#define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw"
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int bluetooth_get_status(void)
|
2008-07-21 20:15:50 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_bluetoothemul)
|
|
|
|
return (tpacpi_bluetooth_emulstate) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2008-07-21 20:15:50 +08:00
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int bluetooth_set_status(enum tpacpi_rfkill_state state)
|
2008-07-21 20:15:51 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_RFKILL,
|
2009-06-02 19:01:37 +08:00
|
|
|
"will attempt to %s bluetooth\n",
|
|
|
|
(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
|
2009-04-04 12:25:50 +08:00
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_bluetoothemul) {
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_bluetooth_emulstate = (state == TPACPI_RFK_RADIO_ON);
|
2009-01-11 13:01:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
if (state == TPACPI_RFK_RADIO_ON)
|
thinkpad-acpi: fix bluetooth/wwan resume
Studying the DSDTs of various thinkpads, it looks like bit 3 of the
argument to SBDC and SWAN is not "set radio to last state on resume".
Rather, it seems to be "if this bit is set, enable radio on resume,
otherwise disable it on resume".
So, the proper way to prepare the radios for S3 suspend is: disable
radio and clear bit 3 on the SBDC/SWAN call to to resume with radio
disabled, and enable radio and set bit 3 on the SBDC/SWAN call to
resume with the radio enabled.
Also, for persistent devices, the rfkill core does not restore state,
so we really need to get the firmware to do the right thing.
We don't sync the radio state on suspend, instead we trust the BIOS to
not do anything weird if we never touched the radio state since boot.
Time will tell if that's a wise way of doing things...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: stable@kernel.org
2010-02-26 09:22:07 +08:00
|
|
|
status = TP_ACPI_BLUETOOTH_RADIOSSW
|
|
|
|
| TP_ACPI_BLUETOOTH_RESUMECTRL;
|
|
|
|
else
|
|
|
|
status = 0;
|
2009-06-02 19:01:37 +08:00
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* sysfs bluetooth enable ---------------------------------------------- */
|
|
|
|
static ssize_t bluetooth_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_BLUETOOTH_SW_ID,
|
|
|
|
attr, buf);
|
2007-04-28 09:00:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t bluetooth_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_BLUETOOTH_SW_ID,
|
|
|
|
attr, buf, count);
|
2007-04-28 09:00:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_bluetooth_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(bluetooth_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:17 +08:00
|
|
|
bluetooth_enable_show, bluetooth_enable_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static struct attribute *bluetooth_attributes[] = {
|
|
|
|
&dev_attr_bluetooth_enable.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group bluetooth_attr_group = {
|
|
|
|
.attrs = bluetooth_attributes,
|
|
|
|
};
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static const struct tpacpi_rfk_ops bluetooth_tprfk_ops = {
|
|
|
|
.get_status = bluetooth_get_status,
|
|
|
|
.set_status = bluetooth_set_status,
|
|
|
|
};
|
2008-07-21 20:15:51 +08:00
|
|
|
|
2009-01-11 13:01:02 +08:00
|
|
|
static void bluetooth_shutdown(void)
|
|
|
|
{
|
|
|
|
/* Order firmware to save current state to NVRAM */
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
|
|
|
|
TP_ACPI_BLTH_SAVE_STATE))
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("failed to save bluetooth state to NVRAM\n");
|
2009-04-04 12:25:50 +08:00
|
|
|
else
|
|
|
|
vdbg_printk(TPACPI_DBG_RFKILL,
|
2011-10-07 04:57:56 +08:00
|
|
|
"bluetooth state saved to NVRAM\n");
|
2009-01-11 13:01:02 +08:00
|
|
|
}
|
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
static void bluetooth_exit(void)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&bluetooth_attr_group);
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
|
|
|
|
|
|
|
|
bluetooth_shutdown();
|
2008-07-21 20:15:50 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init bluetooth_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-04-28 09:00:17 +08:00
|
|
|
int res;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = 0;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"initializing bluetooth subdriver\n");
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(hkey);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
|
|
|
|
G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.bluetooth = hkey_handle &&
|
2007-04-21 22:08:40 +08:00
|
|
|
acpi_evalf(hkey_handle, &status, "GBDC", "qd");
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"bluetooth is %s, status 0x%02x\n",
|
2007-04-21 22:08:40 +08:00
|
|
|
str_supported(tp_features.bluetooth),
|
|
|
|
status);
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_bluetoothemul) {
|
|
|
|
tp_features.bluetooth = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("bluetooth switch emulation enabled\n");
|
2009-01-11 13:01:00 +08:00
|
|
|
} else
|
|
|
|
#endif
|
2008-07-21 20:15:49 +08:00
|
|
|
if (tp_features.bluetooth &&
|
|
|
|
!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
|
|
|
|
/* no bluetooth hardware present in system */
|
|
|
|
tp_features.bluetooth = 0;
|
2009-04-04 12:25:50 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
2008-07-21 20:15:49 +08:00
|
|
|
"bluetooth hardware not installed\n");
|
|
|
|
}
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
if (!tp_features.bluetooth)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
|
2009-06-02 19:01:37 +08:00
|
|
|
&bluetooth_tprfk_ops,
|
2008-07-21 20:15:51 +08:00
|
|
|
RFKILL_TYPE_BLUETOOTH,
|
2009-04-04 12:25:50 +08:00
|
|
|
TPACPI_RFK_BLUETOOTH_SW_NAME,
|
2009-06-02 19:01:37 +08:00
|
|
|
true);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&bluetooth_attr_group);
|
2008-07-21 20:15:51 +08:00
|
|
|
if (res) {
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
|
2008-07-21 20:15:51 +08:00
|
|
|
return res;
|
2007-04-21 22:08:40 +08:00
|
|
|
}
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2009-12-16 07:51:12 +08:00
|
|
|
static int bluetooth_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2009-12-16 07:51:12 +08:00
|
|
|
return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int bluetooth_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_procfs_write(TPACPI_RFK_BLUETOOTH_SW_ID, buf);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct bluetooth_driver_data = {
|
|
|
|
.name = "bluetooth",
|
|
|
|
.read = bluetooth_read,
|
|
|
|
.write = bluetooth_write,
|
2007-04-28 09:00:17 +08:00
|
|
|
.exit = bluetooth_exit,
|
2009-01-11 13:01:02 +08:00
|
|
|
.shutdown = bluetooth_shutdown,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Wan subdriver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
enum {
|
|
|
|
/* ACPI GWAN/SWAN bits */
|
|
|
|
TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */
|
|
|
|
TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */
|
2009-01-11 13:01:01 +08:00
|
|
|
TP_ACPI_WANCARD_RESUMECTRL = 0x04, /* Wan state at resume:
|
thinkpad-acpi: fix bluetooth/wwan resume
Studying the DSDTs of various thinkpads, it looks like bit 3 of the
argument to SBDC and SWAN is not "set radio to last state on resume".
Rather, it seems to be "if this bit is set, enable radio on resume,
otherwise disable it on resume".
So, the proper way to prepare the radios for S3 suspend is: disable
radio and clear bit 3 on the SBDC/SWAN call to to resume with radio
disabled, and enable radio and set bit 3 on the SBDC/SWAN call to
resume with the radio enabled.
Also, for persistent devices, the rfkill core does not restore state,
so we really need to get the firmware to do the right thing.
We don't sync the radio state on suspend, instead we trust the BIOS to
not do anything weird if we never touched the radio state since boot.
Time will tell if that's a wise way of doing things...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: stable@kernel.org
2010-02-26 09:22:07 +08:00
|
|
|
0 = disable, 1 = enable */
|
2008-01-08 23:02:44 +08:00
|
|
|
};
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
#define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw"
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int wan_get_status(void)
|
2008-07-21 20:15:50 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_wwanemul)
|
|
|
|
return (tpacpi_wwan_emulstate) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2008-07-21 20:15:51 +08:00
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int wan_set_status(enum tpacpi_rfkill_state state)
|
2008-07-21 20:15:50 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_RFKILL,
|
2009-06-02 19:01:37 +08:00
|
|
|
"will attempt to %s wwan\n",
|
|
|
|
(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
|
2009-04-04 12:25:50 +08:00
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_wwanemul) {
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_wwan_emulstate = (state == TPACPI_RFK_RADIO_ON);
|
2009-01-11 13:01:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
if (state == TPACPI_RFK_RADIO_ON)
|
thinkpad-acpi: fix bluetooth/wwan resume
Studying the DSDTs of various thinkpads, it looks like bit 3 of the
argument to SBDC and SWAN is not "set radio to last state on resume".
Rather, it seems to be "if this bit is set, enable radio on resume,
otherwise disable it on resume".
So, the proper way to prepare the radios for S3 suspend is: disable
radio and clear bit 3 on the SBDC/SWAN call to to resume with radio
disabled, and enable radio and set bit 3 on the SBDC/SWAN call to
resume with the radio enabled.
Also, for persistent devices, the rfkill core does not restore state,
so we really need to get the firmware to do the right thing.
We don't sync the radio state on suspend, instead we trust the BIOS to
not do anything weird if we never touched the radio state since boot.
Time will tell if that's a wise way of doing things...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: stable@kernel.org
2010-02-26 09:22:07 +08:00
|
|
|
status = TP_ACPI_WANCARD_RADIOSSW
|
|
|
|
| TP_ACPI_WANCARD_RESUMECTRL;
|
|
|
|
else
|
|
|
|
status = 0;
|
2009-06-02 19:01:37 +08:00
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* sysfs wan enable ---------------------------------------------------- */
|
|
|
|
static ssize_t wan_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_WWAN_SW_ID,
|
|
|
|
attr, buf);
|
2007-04-28 09:00:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t wan_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_WWAN_SW_ID,
|
|
|
|
attr, buf, count);
|
2007-04-28 09:00:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_wan_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(wwan_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:17 +08:00
|
|
|
wan_enable_show, wan_enable_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static struct attribute *wan_attributes[] = {
|
|
|
|
&dev_attr_wan_enable.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group wan_attr_group = {
|
|
|
|
.attrs = wan_attributes,
|
|
|
|
};
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static const struct tpacpi_rfk_ops wan_tprfk_ops = {
|
|
|
|
.get_status = wan_get_status,
|
|
|
|
.set_status = wan_set_status,
|
|
|
|
};
|
2008-07-21 20:15:51 +08:00
|
|
|
|
2009-01-11 13:01:02 +08:00
|
|
|
static void wan_shutdown(void)
|
|
|
|
{
|
|
|
|
/* Order firmware to save current state to NVRAM */
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\WGSV", "vd",
|
|
|
|
TP_ACPI_WGSV_SAVE_STATE))
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("failed to save WWAN state to NVRAM\n");
|
2009-04-04 12:25:50 +08:00
|
|
|
else
|
|
|
|
vdbg_printk(TPACPI_DBG_RFKILL,
|
|
|
|
"WWAN state saved to NVRAM\n");
|
2009-01-11 13:01:02 +08:00
|
|
|
}
|
|
|
|
|
2008-07-21 20:15:50 +08:00
|
|
|
static void wan_exit(void)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&wan_attr_group);
|
2009-06-02 19:01:37 +08:00
|
|
|
|
|
|
|
tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
|
|
|
|
|
|
|
|
wan_shutdown();
|
2008-07-21 20:15:50 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init wan_init(struct ibm_init_struct *iibm)
|
2006-06-02 05:41:00 +08:00
|
|
|
{
|
2007-04-28 09:00:17 +08:00
|
|
|
int res;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = 0;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"initializing wan subdriver\n");
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(hkey);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.wan = hkey_handle &&
|
2007-04-21 22:08:40 +08:00
|
|
|
acpi_evalf(hkey_handle, &status, "GWAN", "qd");
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"wan is %s, status 0x%02x\n",
|
2007-04-21 22:08:40 +08:00
|
|
|
str_supported(tp_features.wan),
|
|
|
|
status);
|
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_wwanemul) {
|
|
|
|
tp_features.wan = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("wwan switch emulation enabled\n");
|
2009-01-11 13:01:00 +08:00
|
|
|
} else
|
|
|
|
#endif
|
2008-07-21 20:15:49 +08:00
|
|
|
if (tp_features.wan &&
|
|
|
|
!(status & TP_ACPI_WANCARD_HWPRESENT)) {
|
|
|
|
/* no wan hardware present in system */
|
|
|
|
tp_features.wan = 0;
|
2009-04-04 12:25:50 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
2008-07-21 20:15:49 +08:00
|
|
|
"wan hardware not installed\n");
|
|
|
|
}
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
if (!tp_features.wan)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
|
2009-06-02 19:01:37 +08:00
|
|
|
&wan_tprfk_ops,
|
2008-07-21 20:15:51 +08:00
|
|
|
RFKILL_TYPE_WWAN,
|
2009-04-04 12:25:50 +08:00
|
|
|
TPACPI_RFK_WWAN_SW_NAME,
|
2009-06-02 19:01:37 +08:00
|
|
|
true);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&wan_attr_group);
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
if (res) {
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
|
2008-07-21 20:15:51 +08:00
|
|
|
return res;
|
2007-04-21 22:08:40 +08:00
|
|
|
}
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
return 0;
|
2006-06-02 05:41:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2009-12-16 07:51:12 +08:00
|
|
|
static int wan_read(struct seq_file *m)
|
2006-06-02 05:41:00 +08:00
|
|
|
{
|
2009-12-16 07:51:12 +08:00
|
|
|
return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
|
2006-06-02 05:41:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int wan_write(char *buf)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
return tpacpi_rfk_procfs_write(TPACPI_RFK_WWAN_SW_ID, buf);
|
2006-06-02 05:41:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct wan_driver_data = {
|
|
|
|
.name = "wan",
|
|
|
|
.read = wan_read,
|
|
|
|
.write = wan_write,
|
2007-04-28 09:00:17 +08:00
|
|
|
.exit = wan_exit,
|
2009-01-11 13:01:02 +08:00
|
|
|
.shutdown = wan_shutdown,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2009-01-11 13:01:03 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* UWB subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum {
|
|
|
|
/* ACPI GUWB/SUWB bits */
|
|
|
|
TP_ACPI_UWB_HWPRESENT = 0x01, /* UWB hw available */
|
|
|
|
TP_ACPI_UWB_RADIOSSW = 0x02, /* UWB radio enabled */
|
|
|
|
};
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
#define TPACPI_RFK_UWB_SW_NAME "tpacpi_uwb_sw"
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int uwb_get_status(void)
|
2009-01-11 13:01:03 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_uwbemul)
|
|
|
|
return (tpacpi_uwb_emulstate) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2009-01-11 13:01:03 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GUWB", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ?
|
2009-06-02 19:01:37 +08:00
|
|
|
TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
|
2009-01-11 13:01:03 +08:00
|
|
|
}
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static int uwb_set_status(enum tpacpi_rfkill_state state)
|
2009-01-11 13:01:03 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_RFKILL,
|
2009-06-02 19:01:37 +08:00
|
|
|
"will attempt to %s UWB\n",
|
|
|
|
(state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
|
2009-04-04 12:25:50 +08:00
|
|
|
|
2009-01-11 13:01:03 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_uwbemul) {
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_uwb_emulstate = (state == TPACPI_RFK_RADIO_ON);
|
2009-01-11 13:01:03 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
if (state == TPACPI_RFK_RADIO_ON)
|
|
|
|
status = TP_ACPI_UWB_RADIOSSW;
|
|
|
|
else
|
|
|
|
status = 0;
|
|
|
|
|
2009-01-11 13:01:03 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2009-06-02 19:01:37 +08:00
|
|
|
static const struct tpacpi_rfk_ops uwb_tprfk_ops = {
|
|
|
|
.get_status = uwb_get_status,
|
|
|
|
.set_status = uwb_set_status,
|
|
|
|
};
|
2009-01-11 13:01:03 +08:00
|
|
|
|
|
|
|
static void uwb_exit(void)
|
|
|
|
{
|
2009-06-02 19:01:37 +08:00
|
|
|
tpacpi_destroy_rfkill(TPACPI_RFK_UWB_SW_ID);
|
2009-01-11 13:01:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init uwb_init(struct ibm_init_struct *iibm)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
int status = 0;
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"initializing uwb subdriver\n");
|
2009-01-11 13:01:03 +08:00
|
|
|
|
|
|
|
TPACPI_ACPIHANDLE_INIT(hkey);
|
|
|
|
|
|
|
|
tp_features.uwb = hkey_handle &&
|
|
|
|
acpi_evalf(hkey_handle, &status, "GUWB", "qd");
|
|
|
|
|
2009-04-04 12:25:50 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
|
|
|
|
"uwb is %s, status 0x%02x\n",
|
2009-01-11 13:01:03 +08:00
|
|
|
str_supported(tp_features.uwb),
|
|
|
|
status);
|
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
|
|
|
if (dbg_uwbemul) {
|
|
|
|
tp_features.uwb = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("uwb switch emulation enabled\n");
|
2009-01-11 13:01:03 +08:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
if (tp_features.uwb &&
|
|
|
|
!(status & TP_ACPI_UWB_HWPRESENT)) {
|
|
|
|
/* no uwb hardware present in system */
|
|
|
|
tp_features.uwb = 0;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"uwb hardware not installed\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tp_features.uwb)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID,
|
2009-06-02 19:01:37 +08:00
|
|
|
&uwb_tprfk_ops,
|
2009-01-11 13:01:03 +08:00
|
|
|
RFKILL_TYPE_UWB,
|
2009-04-04 12:25:50 +08:00
|
|
|
TPACPI_RFK_UWB_SW_NAME,
|
2009-06-02 19:01:37 +08:00
|
|
|
false);
|
2009-01-11 13:01:03 +08:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ibm_struct uwb_driver_data = {
|
|
|
|
.name = "uwb",
|
|
|
|
.exit = uwb_exit,
|
|
|
|
.flags.experimental = 1,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Video subdriver
|
|
|
|
*/
|
|
|
|
|
2008-02-16 12:17:54 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_VIDEO
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
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 :( */
|
|
|
|
};
|
|
|
|
|
2006-11-26 02:36:00 +08:00
|
|
|
static enum video_access_mode video_supported;
|
|
|
|
static int video_orig_autosw;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
static int video_autosw_get(void);
|
|
|
|
static int video_autosw_set(int enable);
|
|
|
|
|
2011-04-05 01:06:24 +08:00
|
|
|
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.VGA", /* X100e and a few others */
|
|
|
|
"\\_SB.PCI0.AGP.VID", /* all others */
|
|
|
|
); /* R30, R31 */
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init video_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-08-17 12:00:00 +08:00
|
|
|
int ivga;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(vid);
|
2010-05-17 06:45:23 +08:00
|
|
|
if (tpacpi_is_ibm())
|
|
|
|
TPACPI_ACPIHANDLE_INIT(vid2);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
|
|
|
|
/* G41, assume IVGA doesn't change */
|
|
|
|
vid_handle = vid2_handle;
|
|
|
|
|
|
|
|
if (!vid_handle)
|
|
|
|
/* video switching not supported on R30, R31 */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_NONE;
|
2010-05-17 06:45:23 +08:00
|
|
|
else if (tpacpi_is_ibm() &&
|
|
|
|
acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 570 */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_570;
|
2010-05-17 06:45:23 +08:00
|
|
|
else if (tpacpi_is_ibm() &&
|
|
|
|
acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 600e/x, 770e, 770x */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_770;
|
2005-08-17 12:00:00 +08:00
|
|
|
else
|
|
|
|
/* all others */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_NEW;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
|
|
|
|
str_supported(video_supported != TPACPI_VIDEO_NONE),
|
|
|
|
video_supported);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void video_exit(void)
|
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"restoring original video autoswitch mode\n");
|
|
|
|
if (video_autosw_set(video_orig_autosw))
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("error while trying to restore original "
|
2007-04-21 22:08:41 +08:00
|
|
|
"video autoswitch mode\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_get(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
int i;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
|
|
|
|
TP_ACPI_VIDEO_570_PHSCMD))
|
|
|
|
return -EIO;
|
|
|
|
status = i & TP_ACPI_VIDEO_570_PHSMASK;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDL", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_LCD;
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_CRT;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
|
|
|
|
!acpi_evalf(NULL, &i, "\\VCDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_CRT;
|
|
|
|
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
|
|
|
|
!acpi_evalf(NULL, &i, "\\VCDL", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_LCD;
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDD", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_DVI;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_set(int status)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
res = acpi_evalf(NULL, NULL,
|
|
|
|
"\\_SB.PHS2", "vdd",
|
|
|
|
TP_ACPI_VIDEO_570_PHS2CMD,
|
|
|
|
status | TP_ACPI_VIDEO_570_PHS2SET);
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
autosw = video_autosw_get();
|
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(vid_handle, NULL,
|
|
|
|
"ASWT", "vdd", status * 0x100, 0);
|
|
|
|
if (!autosw && video_autosw_set(autosw)) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("video auto-switch left enabled due to error\n");
|
2007-04-21 22:08:41 +08:00
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
|
2008-01-08 23:02:49 +08:00
|
|
|
acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
|
2007-04-21 22:08:41 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return (res)? 0 : -EIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_autosw_get(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw = 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d"))
|
|
|
|
return -EIO;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d"))
|
|
|
|
return -EIO;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return autosw & 1;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_autosw_set(int enable)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
|
|
|
|
return -EIO;
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_cycle(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw = video_autosw_get();
|
|
|
|
int res;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(ec_handle, NULL, "_Q16", "v");
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(vid_handle, NULL, "VSWT", "v");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
if (!autosw && video_autosw_set(autosw)) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("video auto-switch left enabled due to error\n");
|
2007-04-21 22:08:41 +08:00
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return (res)? 0 : -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int video_expand_toggle(void)
|
|
|
|
{
|
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
/* not reached */
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int video_read(struct seq_file *m)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int status, autosw;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NONE) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
|
|
|
return 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2010-02-26 09:22:22 +08:00
|
|
|
/* Even reads can crash X.org, so... */
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
status = video_outputsw_get();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
autosw = video_autosw_get();
|
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tsupported\n");
|
|
|
|
seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
|
|
|
|
seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
|
2007-04-21 22:08:28 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NEW)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
|
|
|
|
seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
|
|
|
|
seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
|
|
|
|
seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NEW)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
|
|
|
|
seq_printf(m, "commands:\tauto_enable, auto_disable\n");
|
|
|
|
seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int video_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int enable, disable, status;
|
2007-04-21 22:08:41 +08:00
|
|
|
int res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NONE)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2010-02-26 09:22:22 +08:00
|
|
|
/* Even reads can crash X.org, let alone writes... */
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
enable = 0;
|
|
|
|
disable = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "lcd_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_LCD;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "lcd_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_LCD;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "crt_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_CRT;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "crt_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_CRT;
|
2007-04-21 22:08:28 +08:00
|
|
|
} else if (video_supported == TPACPI_VIDEO_NEW &&
|
2005-08-17 12:00:00 +08:00
|
|
|
strlencmp(cmd, "dvi_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_DVI;
|
2007-04-21 22:08:28 +08:00
|
|
|
} else if (video_supported == TPACPI_VIDEO_NEW &&
|
2005-08-17 12:00:00 +08:00
|
|
|
strlencmp(cmd, "dvi_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_DVI;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "auto_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "auto_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(0);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "video_switch") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_outputsw_cycle();
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "expand_toggle") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_expand_toggle();
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable || disable) {
|
2007-04-21 22:08:41 +08:00
|
|
|
status = video_outputsw_get();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
res = video_outputsw_set((status & ~disable) | enable);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct video_driver_data = {
|
|
|
|
.name = "video",
|
|
|
|
.read = video_read,
|
|
|
|
.write = video_write,
|
|
|
|
.exit = video_exit,
|
|
|
|
};
|
|
|
|
|
2008-02-16 12:17:54 +08:00
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_VIDEO */
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Light (thinklight) subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */
|
|
|
|
TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2008-04-26 12:02:23 +08:00
|
|
|
static int light_get_status(void)
|
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
|
|
|
|
if (tp_features.light_status) {
|
|
|
|
if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
|
|
|
|
return -EIO;
|
|
|
|
return (!!status);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int light_set_status(int status)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (tp_features.light) {
|
|
|
|
if (cmos_handle) {
|
|
|
|
rc = acpi_evalf(cmos_handle, NULL, NULL, "vd",
|
|
|
|
(status)?
|
|
|
|
TP_CMOS_THINKLIGHT_ON :
|
|
|
|
TP_CMOS_THINKLIGHT_OFF);
|
|
|
|
} else {
|
|
|
|
rc = acpi_evalf(lght_handle, NULL, NULL, "vd",
|
|
|
|
(status)? 1 : 0);
|
|
|
|
}
|
|
|
|
return (rc)? 0 : -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:24 +08:00
|
|
|
static void light_set_status_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct tpacpi_led_classdev *data =
|
|
|
|
container_of(work, struct tpacpi_led_classdev, work);
|
|
|
|
|
|
|
|
if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
|
2009-04-14 10:44:11 +08:00
|
|
|
light_set_status((data->new_state != TPACPI_LED_OFF));
|
2008-04-26 12:02:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void light_sysfs_set(struct led_classdev *led_cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
struct tpacpi_led_classdev *data =
|
|
|
|
container_of(led_cdev,
|
|
|
|
struct tpacpi_led_classdev,
|
|
|
|
led_classdev);
|
2009-04-14 10:44:11 +08:00
|
|
|
data->new_state = (brightness != LED_OFF) ?
|
|
|
|
TPACPI_LED_ON : TPACPI_LED_OFF;
|
2008-04-26 12:02:28 +08:00
|
|
|
queue_work(tpacpi_wq, &data->work);
|
2008-04-26 12:02:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev)
|
|
|
|
{
|
|
|
|
return (light_get_status() == 1)? LED_FULL : LED_OFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct tpacpi_led_classdev tpacpi_led_thinklight = {
|
|
|
|
.led_classdev = {
|
|
|
|
.name = "tpacpi::thinklight",
|
|
|
|
.brightness_set = &light_sysfs_set,
|
|
|
|
.brightness_get = &light_sysfs_get,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init light_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-06-04 10:36:10 +08:00
|
|
|
int rc;
|
2008-04-26 12:02:24 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
|
|
|
|
|
2010-05-17 06:45:23 +08:00
|
|
|
if (tpacpi_is_ibm()) {
|
|
|
|
TPACPI_ACPIHANDLE_INIT(ledb);
|
|
|
|
TPACPI_ACPIHANDLE_INIT(lght);
|
|
|
|
}
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(cmos);
|
2008-04-26 12:02:24 +08:00
|
|
|
INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.light = (cmos_handle || lght_handle) && !ledb_handle;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.light)
|
2005-08-17 12:00:00 +08:00
|
|
|
/* light status not supported on
|
|
|
|
570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.light_status =
|
|
|
|
acpi_evalf(ec_handle, NULL, "KBLT", "qv");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n",
|
|
|
|
str_supported(tp_features.light),
|
|
|
|
str_supported(tp_features.light_status));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-06-04 10:36:10 +08:00
|
|
|
if (!tp_features.light)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
rc = led_classdev_register(&tpacpi_pdev->dev,
|
|
|
|
&tpacpi_led_thinklight.led_classdev);
|
2008-04-26 12:02:24 +08:00
|
|
|
|
|
|
|
if (rc < 0) {
|
|
|
|
tp_features.light = 0;
|
|
|
|
tp_features.light_status = 0;
|
2008-06-04 10:36:10 +08:00
|
|
|
} else {
|
|
|
|
rc = 0;
|
2008-04-26 12:02:24 +08:00
|
|
|
}
|
2008-06-04 10:36:10 +08:00
|
|
|
|
2008-04-26 12:02:24 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void light_exit(void)
|
|
|
|
{
|
|
|
|
led_classdev_unregister(&tpacpi_led_thinklight.led_classdev);
|
|
|
|
if (work_pending(&tpacpi_led_thinklight.work))
|
2008-04-26 12:02:28 +08:00
|
|
|
flush_workqueue(tpacpi_wq);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int light_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-04-26 12:02:23 +08:00
|
|
|
int status;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.light) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
2007-04-21 22:08:36 +08:00
|
|
|
} else if (!tp_features.light_status) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tunknown\n");
|
|
|
|
seq_printf(m, "commands:\ton, off\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
} else {
|
2008-04-26 12:02:23 +08:00
|
|
|
status = light_get_status();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
|
|
|
|
seq_printf(m, "commands:\ton, off\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int light_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
2008-04-26 12:02:23 +08:00
|
|
|
int newstatus = 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.light)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "on") == 0) {
|
2008-04-26 12:02:23 +08:00
|
|
|
newstatus = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "off") == 0) {
|
2008-04-26 12:02:23 +08:00
|
|
|
newstatus = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:23 +08:00
|
|
|
return light_set_status(newstatus);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct light_driver_data = {
|
|
|
|
.name = "light",
|
|
|
|
.read = light_read,
|
|
|
|
.write = light_write,
|
2008-04-26 12:02:24 +08:00
|
|
|
.exit = light_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* CMOS subdriver
|
|
|
|
*/
|
|
|
|
|
2007-04-24 22:48:19 +08:00
|
|
|
/* sysfs cmos_command -------------------------------------------------- */
|
|
|
|
static ssize_t cmos_command_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long cmos_cmd;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 21, &cmos_cmd))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
res = issue_thinkpad_cmos_command(cmos_cmd);
|
|
|
|
return (res)? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_cmos_command =
|
|
|
|
__ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init cmos_init(struct ibm_init_struct *iibm)
|
2007-04-21 22:08:31 +08:00
|
|
|
{
|
2007-04-24 22:48:19 +08:00
|
|
|
int res;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"initializing cmos commands subdriver\n");
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(cmos);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
|
|
|
|
str_supported(cmos_handle != NULL));
|
2007-04-24 22:48:19 +08:00
|
|
|
|
|
|
|
res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (cmos_handle)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:19 +08:00
|
|
|
static void cmos_exit(void)
|
|
|
|
{
|
|
|
|
device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int cmos_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-08-17 12:00:00 +08:00
|
|
|
/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
|
|
|
|
R30, R31, T20-22, X20-21 */
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!cmos_handle)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
else {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tsupported\n");
|
|
|
|
seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int cmos_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
2007-04-21 22:08:42 +08:00
|
|
|
int cmos_cmd, res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
|
|
|
|
cmos_cmd >= 0 && cmos_cmd <= 21) {
|
2005-04-17 06:20:36 +08:00
|
|
|
/* cmos_cmd set */
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
res = issue_thinkpad_cmos_command(cmos_cmd);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct cmos_driver_data = {
|
|
|
|
.name = "cmos",
|
|
|
|
.read = cmos_read,
|
|
|
|
.write = cmos_write,
|
2007-04-24 22:48:19 +08:00
|
|
|
.exit = cmos_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* LED subdriver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
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 */
|
|
|
|
};
|
|
|
|
|
2006-11-26 02:36:00 +08:00
|
|
|
static enum led_access_mode led_supported;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2010-05-17 06:45:50 +08:00
|
|
|
static acpi_handle led_handle;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-05-31 00:25:08 +08:00
|
|
|
#define TPACPI_LED_NUMLEDS 16
|
2008-04-26 12:02:25 +08:00
|
|
|
static struct tpacpi_led_classdev *tpacpi_leds;
|
|
|
|
static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];
|
2008-05-30 08:51:57 +08:00
|
|
|
static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
|
2008-04-26 12:02:25 +08:00
|
|
|
/* there's a limit of 19 chars + NULL before 2.6.26 */
|
|
|
|
"tpacpi::power",
|
|
|
|
"tpacpi:orange:batt",
|
|
|
|
"tpacpi:green:batt",
|
|
|
|
"tpacpi::dock_active",
|
|
|
|
"tpacpi::bay_active",
|
|
|
|
"tpacpi::dock_batt",
|
|
|
|
"tpacpi::unknown_led",
|
|
|
|
"tpacpi::standby",
|
2009-05-31 00:25:08 +08:00
|
|
|
"tpacpi::dock_status1",
|
|
|
|
"tpacpi::dock_status2",
|
|
|
|
"tpacpi::unknown_led2",
|
|
|
|
"tpacpi::unknown_led3",
|
|
|
|
"tpacpi::thinkvantage",
|
2008-04-26 12:02:25 +08:00
|
|
|
};
|
2009-05-31 00:25:08 +08:00
|
|
|
#define TPACPI_SAFE_LEDS 0x1081U
|
2009-04-04 12:25:49 +08:00
|
|
|
|
|
|
|
static inline bool tpacpi_is_led_restricted(const unsigned int led)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
|
|
|
|
return false;
|
|
|
|
#else
|
2009-05-31 00:25:08 +08:00
|
|
|
return (1U & (TPACPI_SAFE_LEDS >> led)) == 0;
|
2009-04-04 12:25:49 +08:00
|
|
|
#endif
|
|
|
|
}
|
2008-04-26 12:02:25 +08:00
|
|
|
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
static int led_get_status(const unsigned int led)
|
2008-04-26 12:02:23 +08:00
|
|
|
{
|
|
|
|
int status;
|
2008-04-26 12:02:25 +08:00
|
|
|
enum led_status_t led_s;
|
2008-04-26 12:02:23 +08:00
|
|
|
|
|
|
|
switch (led_supported) {
|
|
|
|
case TPACPI_LED_570:
|
|
|
|
if (!acpi_evalf(ec_handle,
|
|
|
|
&status, "GLED", "dd", 1 << led))
|
|
|
|
return -EIO;
|
2008-04-26 12:02:25 +08:00
|
|
|
led_s = (status == 0)?
|
2008-04-26 12:02:23 +08:00
|
|
|
TPACPI_LED_OFF :
|
|
|
|
((status == 1)?
|
|
|
|
TPACPI_LED_ON :
|
|
|
|
TPACPI_LED_BLINK);
|
2008-04-26 12:02:25 +08:00
|
|
|
tpacpi_led_state_cache[led] = led_s;
|
|
|
|
return led_s;
|
2008-04-26 12:02:23 +08:00
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not reached */
|
|
|
|
}
|
|
|
|
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
static int led_set_status(const unsigned int led,
|
|
|
|
const enum led_status_t ledstatus)
|
2008-04-26 12:02:23 +08:00
|
|
|
{
|
|
|
|
/* off, on, blink. Index is led_status_t */
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
static const unsigned int led_sled_arg1[] = { 0, 1, 3 };
|
|
|
|
static const unsigned int led_led_arg1[] = { 0, 0x80, 0xc0 };
|
2008-04-26 12:02:23 +08:00
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
switch (led_supported) {
|
|
|
|
case TPACPI_LED_570:
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
/* 570 */
|
2009-04-04 12:25:49 +08:00
|
|
|
if (unlikely(led > 7))
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
return -EINVAL;
|
2009-04-04 12:25:49 +08:00
|
|
|
if (unlikely(tpacpi_is_led_restricted(led)))
|
|
|
|
return -EPERM;
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
|
|
|
|
(1 << led), led_sled_arg1[ledstatus]))
|
|
|
|
rc = -EIO;
|
|
|
|
break;
|
2008-04-26 12:02:23 +08:00
|
|
|
case TPACPI_LED_OLD:
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
|
2009-04-04 12:25:49 +08:00
|
|
|
if (unlikely(led > 7))
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
return -EINVAL;
|
2009-04-04 12:25:49 +08:00
|
|
|
if (unlikely(tpacpi_is_led_restricted(led)))
|
|
|
|
return -EPERM;
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
rc = ec_write(TPACPI_LED_EC_HLMS, (1 << led));
|
|
|
|
if (rc >= 0)
|
|
|
|
rc = ec_write(TPACPI_LED_EC_HLBL,
|
|
|
|
(ledstatus == TPACPI_LED_BLINK) << led);
|
|
|
|
if (rc >= 0)
|
|
|
|
rc = ec_write(TPACPI_LED_EC_HLCL,
|
|
|
|
(ledstatus != TPACPI_LED_OFF) << led);
|
|
|
|
break;
|
2008-04-26 12:02:23 +08:00
|
|
|
case TPACPI_LED_NEW:
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
/* all others */
|
2009-04-04 12:25:49 +08:00
|
|
|
if (unlikely(led >= TPACPI_LED_NUMLEDS))
|
|
|
|
return -EINVAL;
|
|
|
|
if (unlikely(tpacpi_is_led_restricted(led)))
|
|
|
|
return -EPERM;
|
thinkpad-acpi: fix LED handling on older ThinkPads
The less tested codepaths for LED handling, used on ThinkPads 570, 600e/x,
770e, 770x, A21e, A2xm/p, T20-22, X20 and maybe a few others, would write
data to kernel memory it had no business touching, for leds number 3 and
above. If one is lucky, that illegal write would cause an OOPS, but
chances are it would silently corrupt a byte.
The problem was introduced in commit af116101, "ACPI: thinkpad-acpi: add
sysfs led class support to thinkpad leds (v3.2)".
Fix the bug by refactoring the entire code to be far more obvious on what
it wants to do. Also do some defensive "constification".
Issue reported by Karol Lewandowski <lmctlx@gmail.com> (he's an lucky guy
and got an OOPS instead of silent corruption :-) ).
Root cause of the OOPS identified by Adrian Bunk <bunk@kernel.org>.
Thanks, Adrian!
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Tested-by: Karol Lewandowski <lmctlx@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-06-04 10:36:11 +08:00
|
|
|
if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
|
|
|
|
led, led_led_arg1[ledstatus]))
|
|
|
|
rc = -EIO;
|
|
|
|
break;
|
2008-04-26 12:02:23 +08:00
|
|
|
default:
|
|
|
|
rc = -ENXIO;
|
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:25 +08:00
|
|
|
if (!rc)
|
|
|
|
tpacpi_led_state_cache[led] = ledstatus;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void led_set_status_worker(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct tpacpi_led_classdev *data =
|
|
|
|
container_of(work, struct tpacpi_led_classdev, work);
|
|
|
|
|
|
|
|
if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
|
2009-04-14 10:44:11 +08:00
|
|
|
led_set_status(data->led, data->new_state);
|
2008-04-26 12:02:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void led_sysfs_set(struct led_classdev *led_cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
struct tpacpi_led_classdev *data = container_of(led_cdev,
|
|
|
|
struct tpacpi_led_classdev, led_classdev);
|
|
|
|
|
2009-04-14 10:44:11 +08:00
|
|
|
if (brightness == LED_OFF)
|
|
|
|
data->new_state = TPACPI_LED_OFF;
|
|
|
|
else if (tpacpi_led_state_cache[data->led] != TPACPI_LED_BLINK)
|
|
|
|
data->new_state = TPACPI_LED_ON;
|
|
|
|
else
|
|
|
|
data->new_state = TPACPI_LED_BLINK;
|
|
|
|
|
2008-04-26 12:02:28 +08:00
|
|
|
queue_work(tpacpi_wq, &data->work);
|
2008-04-26 12:02:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int led_sysfs_blink_set(struct led_classdev *led_cdev,
|
|
|
|
unsigned long *delay_on, unsigned long *delay_off)
|
|
|
|
{
|
|
|
|
struct tpacpi_led_classdev *data = container_of(led_cdev,
|
|
|
|
struct tpacpi_led_classdev, led_classdev);
|
|
|
|
|
|
|
|
/* Can we choose the flash rate? */
|
|
|
|
if (*delay_on == 0 && *delay_off == 0) {
|
|
|
|
/* yes. set them to the hardware blink rate (1 Hz) */
|
|
|
|
*delay_on = 500; /* ms */
|
|
|
|
*delay_off = 500; /* ms */
|
|
|
|
} else if ((*delay_on != 500) || (*delay_off != 500))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-14 10:44:11 +08:00
|
|
|
data->new_state = TPACPI_LED_BLINK;
|
2008-04-26 12:02:28 +08:00
|
|
|
queue_work(tpacpi_wq, &data->work);
|
2008-04-26 12:02:25 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness led_sysfs_get(struct led_classdev *led_cdev)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
struct tpacpi_led_classdev *data = container_of(led_cdev,
|
|
|
|
struct tpacpi_led_classdev, led_classdev);
|
|
|
|
|
|
|
|
rc = led_get_status(data->led);
|
|
|
|
|
|
|
|
if (rc == TPACPI_LED_OFF || rc < 0)
|
|
|
|
rc = LED_OFF; /* no error handling in led class :( */
|
|
|
|
else
|
|
|
|
rc = LED_FULL;
|
|
|
|
|
2008-04-26 12:02:23 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:25 +08:00
|
|
|
static void led_exit(void)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
|
|
|
|
if (tpacpi_leds[i].led_classdev.name)
|
|
|
|
led_classdev_unregister(&tpacpi_leds[i].led_classdev);
|
|
|
|
}
|
|
|
|
|
2012-07-25 08:45:07 +08:00
|
|
|
flush_workqueue(tpacpi_wq);
|
2008-04-26 12:02:25 +08:00
|
|
|
kfree(tpacpi_leds);
|
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:49 +08:00
|
|
|
static int __init tpacpi_init_led(unsigned int led)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
tpacpi_leds[led].led = led;
|
|
|
|
|
2009-05-31 00:25:08 +08:00
|
|
|
/* LEDs with no name don't get registered */
|
|
|
|
if (!tpacpi_led_names[led])
|
|
|
|
return 0;
|
|
|
|
|
2009-04-04 12:25:49 +08:00
|
|
|
tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set;
|
|
|
|
tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set;
|
|
|
|
if (led_supported == TPACPI_LED_570)
|
|
|
|
tpacpi_leds[led].led_classdev.brightness_get =
|
|
|
|
&led_sysfs_get;
|
|
|
|
|
|
|
|
tpacpi_leds[led].led_classdev.name = tpacpi_led_names[led];
|
|
|
|
|
|
|
|
INIT_WORK(&tpacpi_leds[led].work, led_set_status_worker);
|
|
|
|
|
|
|
|
rc = led_classdev_register(&tpacpi_pdev->dev,
|
|
|
|
&tpacpi_leds[led].led_classdev);
|
|
|
|
if (rc < 0)
|
|
|
|
tpacpi_leds[led].led_classdev.name = NULL;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2009-05-31 00:25:08 +08:00
|
|
|
static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
|
|
|
|
TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */
|
|
|
|
TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */
|
|
|
|
TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */
|
|
|
|
|
|
|
|
TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */
|
|
|
|
TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */
|
|
|
|
TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */
|
|
|
|
TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */
|
|
|
|
TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */
|
|
|
|
TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */
|
|
|
|
TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */
|
|
|
|
TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */
|
|
|
|
|
|
|
|
TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */
|
|
|
|
TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */
|
|
|
|
TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */
|
|
|
|
TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */
|
|
|
|
TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */
|
|
|
|
|
|
|
|
TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */
|
|
|
|
TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */
|
|
|
|
TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */
|
|
|
|
TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */
|
|
|
|
|
|
|
|
/* (1) - may have excess leds enabled on MSB */
|
|
|
|
|
|
|
|
/* Defaults (order matters, keep last, don't reorder!) */
|
|
|
|
{ /* Lenovo */
|
|
|
|
.vendor = PCI_VENDOR_ID_LENOVO,
|
|
|
|
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = 0x1fffU,
|
|
|
|
},
|
|
|
|
{ /* IBM ThinkPads with no EC version string */
|
|
|
|
.vendor = PCI_VENDOR_ID_IBM,
|
|
|
|
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN,
|
|
|
|
.quirks = 0x00ffU,
|
|
|
|
},
|
|
|
|
{ /* IBM ThinkPads with EC version string */
|
|
|
|
.vendor = PCI_VENDOR_ID_IBM,
|
|
|
|
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = 0x00bfU,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
#undef TPACPI_LEDQ_IBM
|
|
|
|
#undef TPACPI_LEDQ_LNV
|
|
|
|
|
2010-05-17 06:45:50 +08:00
|
|
|
static enum led_access_mode __init led_init_detect_mode(void)
|
|
|
|
{
|
|
|
|
acpi_status status;
|
|
|
|
|
|
|
|
if (tpacpi_is_ibm()) {
|
|
|
|
/* 570 */
|
|
|
|
status = acpi_get_handle(ec_handle, "SLED", &led_handle);
|
|
|
|
if (ACPI_SUCCESS(status))
|
|
|
|
return TPACPI_LED_570;
|
|
|
|
|
|
|
|
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
|
|
|
status = acpi_get_handle(ec_handle, "SYSL", &led_handle);
|
|
|
|
if (ACPI_SUCCESS(status))
|
|
|
|
return TPACPI_LED_OLD;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* most others */
|
|
|
|
status = acpi_get_handle(ec_handle, "LED", &led_handle);
|
|
|
|
if (ACPI_SUCCESS(status))
|
|
|
|
return TPACPI_LED_NEW;
|
|
|
|
|
|
|
|
/* R30, R31, and unknown firmwares */
|
|
|
|
led_handle = NULL;
|
|
|
|
return TPACPI_LED_NONE;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init led_init(struct ibm_init_struct *iibm)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2008-04-26 12:02:25 +08:00
|
|
|
unsigned int i;
|
|
|
|
int rc;
|
2009-05-31 00:25:08 +08:00
|
|
|
unsigned long useful_leds;
|
2008-04-26 12:02:25 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
|
|
|
|
|
2010-05-17 06:45:50 +08:00
|
|
|
led_supported = led_init_detect_mode();
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
|
|
|
|
str_supported(led_supported), led_supported);
|
|
|
|
|
2009-05-31 00:25:08 +08:00
|
|
|
if (led_supported == TPACPI_LED_NONE)
|
|
|
|
return 1;
|
|
|
|
|
2008-04-26 12:02:25 +08:00
|
|
|
tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!tpacpi_leds) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Out of memory for LED data\n");
|
2008-04-26 12:02:25 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2009-05-31 00:25:08 +08:00
|
|
|
useful_leds = tpacpi_check_quirks(led_useful_qtable,
|
|
|
|
ARRAY_SIZE(led_useful_qtable));
|
|
|
|
|
2008-04-26 12:02:25 +08:00
|
|
|
for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
|
2009-05-31 00:25:08 +08:00
|
|
|
if (!tpacpi_is_led_restricted(i) &&
|
|
|
|
test_bit(i, &useful_leds)) {
|
2009-04-04 12:25:49 +08:00
|
|
|
rc = tpacpi_init_led(i);
|
|
|
|
if (rc < 0) {
|
|
|
|
led_exit();
|
|
|
|
return rc;
|
|
|
|
}
|
2008-04-26 12:02:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:49 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("warning: userspace override of important "
|
|
|
|
"firmware LEDs is enabled\n");
|
2009-04-04 12:25:49 +08:00
|
|
|
#endif
|
2009-05-31 00:25:08 +08:00
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:23 +08:00
|
|
|
#define str_led_status(s) \
|
|
|
|
((s) == TPACPI_LED_OFF ? "off" : \
|
|
|
|
((s) == TPACPI_LED_ON ? "on" : "blinking"))
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int led_read(struct seq_file *m)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!led_supported) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tsupported\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (led_supported == TPACPI_LED_570) {
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 570 */
|
|
|
|
int i, status;
|
|
|
|
for (i = 0; i < 8; i++) {
|
2008-04-26 12:02:23 +08:00
|
|
|
status = led_get_status(i);
|
|
|
|
if (status < 0)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -EIO;
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "%d:\t\t%s\n",
|
2008-04-26 12:02:23 +08:00
|
|
|
i, str_led_status(status));
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\t"
|
2009-05-31 00:25:08 +08:00
|
|
|
"<led> on, <led> off, <led> blink (<led> is 0-15)\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int led_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
2008-04-26 12:02:23 +08:00
|
|
|
int led, rc;
|
|
|
|
enum led_status_t s;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
if (!led_supported)
|
|
|
|
return -ENODEV;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2009-05-31 00:25:08 +08:00
|
|
|
if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (strstr(cmd, "off")) {
|
2008-04-26 12:02:23 +08:00
|
|
|
s = TPACPI_LED_OFF;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strstr(cmd, "on")) {
|
2008-04-26 12:02:23 +08:00
|
|
|
s = TPACPI_LED_ON;
|
2005-08-17 12:00:00 +08:00
|
|
|
} else if (strstr(cmd, "blink")) {
|
2008-04-26 12:02:23 +08:00
|
|
|
s = TPACPI_LED_BLINK;
|
2005-08-17 12:00:00 +08:00
|
|
|
} else {
|
2008-04-26 12:02:23 +08:00
|
|
|
return -EINVAL;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2008-04-26 12:02:23 +08:00
|
|
|
|
|
|
|
rc = led_set_status(led, s);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct led_driver_data = {
|
|
|
|
.name = "led",
|
|
|
|
.read = led_read,
|
|
|
|
.write = led_write,
|
2008-04-26 12:02:25 +08:00
|
|
|
.exit = led_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Beep subdriver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-05-31 00:25:07 +08:00
|
|
|
#define TPACPI_BEEP_Q1 0x0001
|
|
|
|
|
|
|
|
static const struct tpacpi_quirk beep_quirk_table[] __initconst = {
|
|
|
|
TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */
|
|
|
|
TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init beep_init(struct ibm_init_struct *iibm)
|
2007-04-21 22:08:31 +08:00
|
|
|
{
|
2009-05-31 00:25:07 +08:00
|
|
|
unsigned long quirks;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(beep);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
|
|
|
|
str_supported(beep_handle != NULL));
|
|
|
|
|
2009-05-31 00:25:07 +08:00
|
|
|
quirks = tpacpi_check_quirks(beep_quirk_table,
|
|
|
|
ARRAY_SIZE(beep_quirk_table));
|
|
|
|
|
|
|
|
tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (beep_handle)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int beep_read(struct seq_file *m)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
|
|
|
if (!beep_handle)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
else {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tsupported\n");
|
|
|
|
seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int beep_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int beep_cmd;
|
|
|
|
|
|
|
|
if (!beep_handle)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
|
|
|
|
beep_cmd >= 0 && beep_cmd <= 17) {
|
|
|
|
/* beep_cmd set */
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
2009-05-31 00:25:07 +08:00
|
|
|
if (tp_features.beep_needs_two_args) {
|
|
|
|
if (!acpi_evalf(beep_handle, NULL, NULL, "vdd",
|
|
|
|
beep_cmd, 0))
|
|
|
|
return -EIO;
|
|
|
|
} else {
|
|
|
|
if (!acpi_evalf(beep_handle, NULL, NULL, "vd",
|
|
|
|
beep_cmd))
|
|
|
|
return -EIO;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct beep_driver_data = {
|
|
|
|
.name = "beep",
|
|
|
|
.read = beep_read,
|
|
|
|
.write = beep_write,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Thermal subdriver
|
|
|
|
*/
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
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 */
|
2009-12-09 09:36:28 +08:00
|
|
|
|
|
|
|
TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */
|
2008-01-08 23:02:44 +08:00
|
|
|
};
|
|
|
|
|
2009-12-09 09:36:28 +08:00
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
|
|
|
|
struct ibm_thermal_sensors_struct {
|
|
|
|
s32 temp[TPACPI_MAX_THERMAL_SENSORS];
|
|
|
|
};
|
|
|
|
|
2006-11-24 21:47:08 +08:00
|
|
|
static enum thermal_access_mode thermal_read_mode;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* 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;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
for (i = 0 ; i < n; i++) {
|
2008-01-08 23:02:45 +08:00
|
|
|
res = thermal_get_sensor(i, &s->temp[i]);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2009-12-09 09:36:28 +08:00
|
|
|
static void thermal_dump_all_sensors(void)
|
|
|
|
{
|
|
|
|
int n, i;
|
|
|
|
struct ibm_thermal_sensors_struct t;
|
|
|
|
|
|
|
|
n = thermal_get_sensors(&t);
|
|
|
|
if (n <= 0)
|
|
|
|
return;
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("temperatures (Celsius):");
|
2009-12-09 09:36:28 +08:00
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_cont(" %d", (int)(t.temp[i] / 1000));
|
2009-12-09 09:36:28 +08:00
|
|
|
else
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_cont(" N/A");
|
2009-12-09 09:36:28 +08:00
|
|
|
}
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_cont("\n");
|
2009-12-09 09:36:28 +08:00
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:16 +08:00
|
|
|
/* sysfs temp##_input -------------------------------------------------- */
|
|
|
|
|
|
|
|
static ssize_t thermal_temp_input_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct sensor_device_attribute *sensor_attr =
|
|
|
|
to_sensor_dev_attr(attr);
|
|
|
|
int idx = sensor_attr->index;
|
|
|
|
s32 value;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
res = thermal_get_sensor(idx, &value);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2009-12-09 09:36:28 +08:00
|
|
|
if (value == TPACPI_THERMAL_SENSOR_NA)
|
2007-04-24 22:48:16 +08:00
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
|
2008-01-08 23:02:49 +08:00
|
|
|
SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, \
|
|
|
|
thermal_temp_input_show, NULL, _idxB)
|
2007-04-24 22:48:16 +08:00
|
|
|
|
|
|
|
static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(1, 0),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(2, 1),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(3, 2),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(4, 3),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(5, 4),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(6, 5),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(7, 6),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(8, 7),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(9, 8),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(10, 9),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(11, 10),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(12, 11),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(13, 12),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(14, 13),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(15, 14),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(16, 15),
|
|
|
|
};
|
|
|
|
|
|
|
|
#define THERMAL_ATTRS(X) \
|
|
|
|
&sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
|
|
|
|
|
|
|
|
static struct attribute *thermal_temp_input_attr[] = {
|
|
|
|
THERMAL_ATTRS(8),
|
|
|
|
THERMAL_ATTRS(9),
|
|
|
|
THERMAL_ATTRS(10),
|
|
|
|
THERMAL_ATTRS(11),
|
|
|
|
THERMAL_ATTRS(12),
|
|
|
|
THERMAL_ATTRS(13),
|
|
|
|
THERMAL_ATTRS(14),
|
|
|
|
THERMAL_ATTRS(15),
|
|
|
|
THERMAL_ATTRS(0),
|
|
|
|
THERMAL_ATTRS(1),
|
|
|
|
THERMAL_ATTRS(2),
|
|
|
|
THERMAL_ATTRS(3),
|
|
|
|
THERMAL_ATTRS(4),
|
|
|
|
THERMAL_ATTRS(5),
|
|
|
|
THERMAL_ATTRS(6),
|
|
|
|
THERMAL_ATTRS(7),
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group thermal_temp_input16_group = {
|
|
|
|
.attrs = thermal_temp_input_attr
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group thermal_temp_input8_group = {
|
|
|
|
.attrs = &thermal_temp_input_attr[8]
|
|
|
|
};
|
|
|
|
|
|
|
|
#undef THERMAL_SENSOR_ATTR_TEMP
|
|
|
|
#undef THERMAL_ATTRS
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init thermal_init(struct ibm_init_struct *iibm)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2006-11-24 21:47:08 +08:00
|
|
|
u8 t, ta1, ta2;
|
|
|
|
int i;
|
2007-04-21 22:08:31 +08:00
|
|
|
int acpi_tmp7;
|
2007-04-24 22:48:16 +08:00
|
|
|
int res;
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
|
2006-11-24 21:47:08 +08:00
|
|
|
|
2007-07-19 10:45:46 +08:00
|
|
|
if (thinkpad_id.ec_model) {
|
2006-11-24 21:47:08 +08:00
|
|
|
/*
|
|
|
|
* Direct EC access mode: sensors at registers
|
|
|
|
* 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
|
|
|
|
* non-implemented, thermal sensors return 0x80 when
|
|
|
|
* not available
|
|
|
|
*/
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:08 +08:00
|
|
|
ta1 = ta2 = 0;
|
|
|
|
for (i = 0; i < 8; i++) {
|
2007-04-21 22:08:43 +08:00
|
|
|
if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
|
2006-11-24 21:47:08 +08:00
|
|
|
ta1 |= t;
|
|
|
|
} else {
|
|
|
|
ta1 = 0;
|
|
|
|
break;
|
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
|
2006-11-24 21:47:08 +08:00
|
|
|
ta2 |= t;
|
|
|
|
} else {
|
|
|
|
ta1 = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ta1 == 0) {
|
|
|
|
/* This is sheer paranoia, but we handle it anyway */
|
|
|
|
if (acpi_tmp7) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("ThinkPad ACPI EC access misbehaving, "
|
2008-01-08 23:02:49 +08:00
|
|
|
"falling back to ACPI TMPx access "
|
|
|
|
"mode\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
|
2006-11-24 21:47:08 +08:00
|
|
|
} else {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("ThinkPad ACPI EC access misbehaving, "
|
2006-11-24 21:47:08 +08:00
|
|
|
"disabling thermal sensors access\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_NONE;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
thermal_read_mode =
|
|
|
|
(ta2 != 0) ?
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else if (acpi_tmp7) {
|
2010-05-17 06:45:23 +08:00
|
|
|
if (tpacpi_is_ibm() &&
|
|
|
|
acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
|
2006-11-24 21:47:08 +08:00
|
|
|
/* 600e/x, 770e, 770x */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
|
2006-11-24 21:47:08 +08:00
|
|
|
} else {
|
2010-05-17 06:45:23 +08:00
|
|
|
/* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* temperatures not supported on 570, G4x, R30, R31, R32 */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_NONE;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
|
|
|
|
str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
|
|
|
|
thermal_read_mode);
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
switch (thermal_read_mode) {
|
2007-04-24 22:48:16 +08:00
|
|
|
case TPACPI_THERMAL_TPEC_16:
|
2007-09-25 17:38:03 +08:00
|
|
|
res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input16_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_TPEC_8:
|
|
|
|
case TPACPI_THERMAL_ACPI_TMP07:
|
|
|
|
case TPACPI_THERMAL_ACPI_UPDT:
|
2007-09-25 17:38:03 +08:00
|
|
|
res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input8_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_NONE:
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void thermal_exit(void)
|
|
|
|
{
|
2008-01-08 23:02:49 +08:00
|
|
|
switch (thermal_read_mode) {
|
2007-04-24 22:48:16 +08:00
|
|
|
case TPACPI_THERMAL_TPEC_16:
|
2007-09-25 17:38:03 +08:00
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input16_group);
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_TPEC_8:
|
|
|
|
case TPACPI_THERMAL_ACPI_TMP07:
|
|
|
|
case TPACPI_THERMAL_ACPI_UPDT:
|
2007-09-25 17:38:03 +08:00
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
|
2010-02-03 06:37:58 +08:00
|
|
|
&thermal_temp_input8_group);
|
2007-04-24 22:48:16 +08:00
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_NONE:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int thermal_read(struct seq_file *m)
|
2006-11-24 21:47:08 +08:00
|
|
|
{
|
|
|
|
int n, i;
|
|
|
|
struct ibm_thermal_sensors_struct t;
|
|
|
|
|
|
|
|
n = thermal_get_sensors(&t);
|
|
|
|
if (unlikely(n < 0))
|
|
|
|
return n;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "temperatures:\t");
|
2006-11-24 21:47:08 +08:00
|
|
|
|
|
|
|
if (n > 0) {
|
|
|
|
for (i = 0; i < (n - 1); i++)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "%d ", t.temp[i] / 1000);
|
|
|
|
seq_printf(m, "%d\n", t.temp[i] / 1000);
|
2006-11-24 21:47:08 +08:00
|
|
|
} else
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "not supported\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct thermal_driver_data = {
|
|
|
|
.name = "thermal",
|
|
|
|
.read = thermal_read,
|
2007-04-24 22:48:16 +08:00
|
|
|
.exit = thermal_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Backlight/brightness subdriver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
/*
|
|
|
|
* ThinkPads can read brightness from two places: EC HBRV (0x31), or
|
|
|
|
* CMOS NVRAM byte 0x5E, bits 0-3.
|
|
|
|
*
|
|
|
|
* EC HBRV (0x31) has the following layout
|
|
|
|
* Bit 7: unknown function
|
|
|
|
* Bit 6: unknown function
|
|
|
|
* Bit 5: Z: honour scale changes, NZ: ignore scale changes
|
|
|
|
* Bit 4: must be set to zero to avoid problems
|
|
|
|
* Bit 3-0: backlight brightness level
|
|
|
|
*
|
|
|
|
* brightness_get_raw returns status data in the HBRV layout
|
2009-06-18 11:40:16 +08:00
|
|
|
*
|
|
|
|
* WARNING: The X61 has been verified to use HBRV for something else, so
|
|
|
|
* this should be used _only_ on IBM ThinkPads, and maybe with some careful
|
|
|
|
* testing on the very early *60 Lenovo models...
|
2009-04-04 12:25:53 +08:00
|
|
|
*/
|
|
|
|
|
2008-04-26 12:02:21 +08:00
|
|
|
enum {
|
|
|
|
TP_EC_BACKLIGHT = 0x31,
|
|
|
|
|
|
|
|
/* TP_EC_BACKLIGHT bitmasks */
|
|
|
|
TP_EC_BACKLIGHT_LVLMSK = 0x1F,
|
|
|
|
TP_EC_BACKLIGHT_CMDMSK = 0xE0,
|
|
|
|
TP_EC_BACKLIGHT_MAPSW = 0x20,
|
|
|
|
};
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
enum tpacpi_brightness_access_mode {
|
|
|
|
TPACPI_BRGHT_MODE_AUTO = 0, /* Not implemented yet */
|
|
|
|
TPACPI_BRGHT_MODE_EC, /* EC control */
|
|
|
|
TPACPI_BRGHT_MODE_UCMS_STEP, /* UCMS step-based control */
|
|
|
|
TPACPI_BRGHT_MODE_ECNVRAM, /* EC control w/ NVRAM store */
|
|
|
|
TPACPI_BRGHT_MODE_MAX
|
|
|
|
};
|
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct backlight_device *ibm_backlight_device;
|
2009-04-04 12:25:53 +08:00
|
|
|
|
|
|
|
static enum tpacpi_brightness_access_mode brightness_mode =
|
|
|
|
TPACPI_BRGHT_MODE_MAX;
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static struct mutex brightness_mutex;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
/* NVRAM brightness access,
|
|
|
|
* call with brightness_mutex held! */
|
|
|
|
static unsigned int tpacpi_brightness_nvram_get(void)
|
2008-01-08 23:02:45 +08:00
|
|
|
{
|
2009-04-04 12:25:53 +08:00
|
|
|
u8 lnvram;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
|
|
|
|
& TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
|
|
|
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
|
2010-05-17 06:45:33 +08:00
|
|
|
lnvram &= bright_maxlvl;
|
2009-04-04 12:25:53 +08:00
|
|
|
|
|
|
|
return lnvram;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tpacpi_brightness_checkpoint_nvram(void)
|
|
|
|
{
|
|
|
|
u8 lec = 0;
|
|
|
|
u8 b_nvram;
|
|
|
|
|
|
|
|
if (brightness_mode != TPACPI_BRGHT_MODE_ECNVRAM)
|
|
|
|
return;
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_BRGHT,
|
|
|
|
"trying to checkpoint backlight level to NVRAM...\n");
|
|
|
|
|
|
|
|
if (mutex_lock_killable(&brightness_mutex) < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
|
|
|
|
goto unlock;
|
|
|
|
lec &= TP_EC_BACKLIGHT_LVLMSK;
|
|
|
|
b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
|
|
|
|
|
|
|
|
if (lec != ((b_nvram & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
|
|
|
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS)) {
|
|
|
|
/* NVRAM needs update */
|
|
|
|
b_nvram &= ~(TP_NVRAM_MASK_LEVEL_BRIGHTNESS <<
|
|
|
|
TP_NVRAM_POS_LEVEL_BRIGHTNESS);
|
|
|
|
b_nvram |= lec;
|
|
|
|
nvram_write_byte(b_nvram, TP_NVRAM_ADDR_BRIGHTNESS);
|
|
|
|
dbg_printk(TPACPI_DBG_BRGHT,
|
|
|
|
"updated NVRAM backlight level to %u (0x%02x)\n",
|
|
|
|
(unsigned int) lec, (unsigned int) b_nvram);
|
|
|
|
} else
|
|
|
|
vdbg_printk(TPACPI_DBG_BRGHT,
|
|
|
|
"NVRAM backlight level already is %u (0x%02x)\n",
|
|
|
|
(unsigned int) lec, (unsigned int) b_nvram);
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&brightness_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* call with brightness_mutex held! */
|
|
|
|
static int tpacpi_brightness_get_raw(int *status)
|
|
|
|
{
|
|
|
|
u8 lec = 0;
|
|
|
|
|
|
|
|
switch (brightness_mode) {
|
|
|
|
case TPACPI_BRGHT_MODE_UCMS_STEP:
|
|
|
|
*status = tpacpi_brightness_nvram_get();
|
|
|
|
return 0;
|
|
|
|
case TPACPI_BRGHT_MODE_EC:
|
|
|
|
case TPACPI_BRGHT_MODE_ECNVRAM:
|
|
|
|
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
|
2008-04-26 12:02:20 +08:00
|
|
|
return -EIO;
|
2009-04-04 12:25:53 +08:00
|
|
|
*status = lec;
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return -ENXIO;
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
2009-04-04 12:25:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* call with brightness_mutex held! */
|
|
|
|
/* do NOT call with illegal backlight level value */
|
|
|
|
static int tpacpi_brightness_set_ec(unsigned int value)
|
|
|
|
{
|
|
|
|
u8 lec = 0;
|
|
|
|
|
|
|
|
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
if (unlikely(!acpi_ec_write(TP_EC_BACKLIGHT,
|
|
|
|
(lec & TP_EC_BACKLIGHT_CMDMSK) |
|
|
|
|
(value & TP_EC_BACKLIGHT_LVLMSK))))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call with brightness_mutex held! */
|
|
|
|
static int tpacpi_brightness_set_ucmsstep(unsigned int value)
|
|
|
|
{
|
|
|
|
int cmos_cmd, inc;
|
|
|
|
unsigned int current_value, i;
|
|
|
|
|
|
|
|
current_value = tpacpi_brightness_nvram_get();
|
|
|
|
|
|
|
|
if (value == current_value)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cmos_cmd = (value > current_value) ?
|
|
|
|
TP_CMOS_BRIGHTNESS_UP :
|
|
|
|
TP_CMOS_BRIGHTNESS_DOWN;
|
|
|
|
inc = (value > current_value) ? 1 : -1;
|
|
|
|
|
|
|
|
for (i = current_value; i != value; i += inc)
|
|
|
|
if (issue_thinkpad_cmos_command(cmos_cmd))
|
|
|
|
return -EIO;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2008-04-26 12:02:21 +08:00
|
|
|
return 0;
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* May return EINTR which can always be mapped to ERESTARTSYS */
|
2009-04-04 12:25:53 +08:00
|
|
|
static int brightness_set(unsigned int value)
|
2008-01-08 23:02:45 +08:00
|
|
|
{
|
2009-04-04 12:25:53 +08:00
|
|
|
int res;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2010-05-17 06:45:33 +08:00
|
|
|
if (value > bright_maxlvl || value < 0)
|
2008-01-08 23:02:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_BRGHT,
|
|
|
|
"set backlight level to %d\n", value);
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
res = mutex_lock_killable(&brightness_mutex);
|
2008-01-08 23:02:45 +08:00
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
switch (brightness_mode) {
|
|
|
|
case TPACPI_BRGHT_MODE_EC:
|
|
|
|
case TPACPI_BRGHT_MODE_ECNVRAM:
|
|
|
|
res = tpacpi_brightness_set_ec(value);
|
|
|
|
break;
|
|
|
|
case TPACPI_BRGHT_MODE_UCMS_STEP:
|
|
|
|
res = tpacpi_brightness_set_ucmsstep(value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
res = -ENXIO;
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&brightness_mutex);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sysfs backlight class ----------------------------------------------- */
|
|
|
|
|
|
|
|
static int brightness_update_status(struct backlight_device *bd)
|
|
|
|
{
|
2009-04-04 12:25:53 +08:00
|
|
|
unsigned int level =
|
2008-01-08 23:02:45 +08:00
|
|
|
(bd->props.fb_blank == FB_BLANK_UNBLANK &&
|
|
|
|
bd->props.power == FB_BLANK_UNBLANK) ?
|
2009-04-04 12:25:53 +08:00
|
|
|
bd->props.brightness : 0;
|
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_BRGHT,
|
|
|
|
"backlight: attempt to set level to %d\n",
|
|
|
|
level);
|
|
|
|
|
|
|
|
/* it is the backlight class's job (caller) to handle
|
|
|
|
* EINTR and other errors properly */
|
|
|
|
return brightness_set(level);
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:21 +08:00
|
|
|
static int brightness_get(struct backlight_device *bd)
|
2007-10-31 03:46:20 +08:00
|
|
|
{
|
2008-04-26 12:02:21 +08:00
|
|
|
int status, res;
|
2007-10-31 03:46:20 +08:00
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
res = mutex_lock_killable(&brightness_mutex);
|
2008-04-26 12:02:21 +08:00
|
|
|
if (res < 0)
|
2009-04-04 12:25:53 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
res = tpacpi_brightness_get_raw(&status);
|
|
|
|
|
|
|
|
mutex_unlock(&brightness_mutex);
|
|
|
|
|
|
|
|
if (res < 0)
|
|
|
|
return 0;
|
2007-10-31 03:46:22 +08:00
|
|
|
|
2008-04-26 12:02:21 +08:00
|
|
|
return status & TP_EC_BACKLIGHT_LVLMSK;
|
2007-10-31 03:46:22 +08:00
|
|
|
}
|
|
|
|
|
2009-12-09 09:36:24 +08:00
|
|
|
static void tpacpi_brightness_notify_change(void)
|
|
|
|
{
|
|
|
|
backlight_force_update(ibm_backlight_device,
|
|
|
|
BACKLIGHT_UPDATE_HOTKEY);
|
|
|
|
}
|
|
|
|
|
2010-11-16 21:14:02 +08:00
|
|
|
static const struct backlight_ops ibm_backlight_data = {
|
2008-01-08 23:02:49 +08:00
|
|
|
.get_brightness = brightness_get,
|
|
|
|
.update_status = brightness_update_status,
|
2007-03-24 04:33:57 +08:00
|
|
|
};
|
2007-10-31 03:46:22 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* --------------------------------------------------------------------- */
|
2007-10-31 03:46:22 +08:00
|
|
|
|
2010-08-10 10:48:18 +08:00
|
|
|
/*
|
|
|
|
* Call _BCL method of video device. On some ThinkPads this will
|
|
|
|
* switch the firmware to the ACPI brightness control mode.
|
|
|
|
*/
|
|
|
|
|
2010-05-17 06:45:33 +08:00
|
|
|
static int __init tpacpi_query_bcl_levels(acpi_handle handle)
|
|
|
|
{
|
|
|
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
|
|
union acpi_object *obj;
|
|
|
|
int rc;
|
|
|
|
|
2010-08-10 10:48:18 +08:00
|
|
|
if (ACPI_SUCCESS(acpi_evaluate_object(handle, "_BCL", NULL, &buffer))) {
|
2010-05-17 06:45:33 +08:00
|
|
|
obj = (union acpi_object *)buffer.pointer;
|
|
|
|
if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Unknown _BCL data, please report this to %s\n",
|
|
|
|
TPACPI_MAIL);
|
2010-05-17 06:45:33 +08:00
|
|
|
rc = 0;
|
|
|
|
} else {
|
|
|
|
rc = obj->package.count;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(buffer.pointer);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
|
|
|
|
*/
|
|
|
|
static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
|
|
|
|
{
|
2010-08-10 10:48:18 +08:00
|
|
|
acpi_handle video_device;
|
2010-05-17 06:45:33 +08:00
|
|
|
int bcl_levels = 0;
|
|
|
|
|
2010-08-10 10:48:18 +08:00
|
|
|
tpacpi_acpi_handle_locate("video", ACPI_VIDEO_HID, &video_device);
|
|
|
|
if (video_device)
|
|
|
|
bcl_levels = tpacpi_query_bcl_levels(video_device);
|
2010-05-17 06:45:33 +08:00
|
|
|
|
2010-08-10 10:48:18 +08:00
|
|
|
tp_features.bright_acpimode = (bcl_levels > 0);
|
2010-05-17 06:45:33 +08:00
|
|
|
|
2010-08-10 10:48:18 +08:00
|
|
|
return (bcl_levels > 2) ? (bcl_levels - 2) : 0;
|
2010-05-17 06:45:33 +08:00
|
|
|
}
|
|
|
|
|
2009-08-01 23:04:20 +08:00
|
|
|
/*
|
|
|
|
* These are only useful for models that have only one possibility
|
|
|
|
* of GPU. If the BIOS model handles both ATI and Intel, don't use
|
|
|
|
* these quirks.
|
|
|
|
*/
|
|
|
|
#define TPACPI_BRGHT_Q_NOEC 0x0001 /* Must NOT use EC HBRV */
|
|
|
|
#define TPACPI_BRGHT_Q_EC 0x0002 /* Should or must use EC HBRV */
|
|
|
|
#define TPACPI_BRGHT_Q_ASK 0x8000 /* Ask for user report */
|
|
|
|
|
|
|
|
static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
|
|
|
|
/* Models with ATI GPUs known to require ECNVRAM mode */
|
|
|
|
TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */
|
|
|
|
|
2009-09-13 02:22:11 +08:00
|
|
|
/* Models with ATI GPUs that can use ECNVRAM */
|
2010-02-26 08:28:56 +08:00
|
|
|
TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC), /* R50,51 T40-42 */
|
2009-08-01 23:04:20 +08:00
|
|
|
TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
|
2010-02-26 08:28:56 +08:00
|
|
|
TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_EC), /* R52 */
|
2009-08-01 23:04:20 +08:00
|
|
|
TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
|
|
|
|
|
2009-09-13 02:22:11 +08:00
|
|
|
/* Models with Intel Extreme Graphics 2 */
|
2010-02-26 08:28:56 +08:00
|
|
|
TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), /* X40 */
|
2009-12-09 09:36:21 +08:00
|
|
|
TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
|
|
|
|
TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
|
2009-08-01 23:04:20 +08:00
|
|
|
|
|
|
|
/* Models with Intel GMA900 */
|
|
|
|
TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */
|
|
|
|
TPACPI_Q_IBM('7', '4', TPACPI_BRGHT_Q_NOEC), /* X41 */
|
|
|
|
TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC), /* X41 Tablet */
|
|
|
|
};
|
|
|
|
|
2010-05-17 06:45:33 +08:00
|
|
|
/*
|
|
|
|
* Returns < 0 for error, otherwise sets tp_features.bright_*
|
|
|
|
* and bright_maxlvl.
|
|
|
|
*/
|
|
|
|
static void __init tpacpi_detect_brightness_capabilities(void)
|
|
|
|
{
|
|
|
|
unsigned int b;
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"detecting firmware brightness interface capabilities\n");
|
|
|
|
|
|
|
|
/* we could run a quirks check here (same table used by
|
|
|
|
* brightness_init) if needed */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We always attempt to detect acpi support, so as to switch
|
|
|
|
* Lenovo Vista BIOS to ACPI brightness mode even if we are not
|
|
|
|
* going to publish a backlight interface
|
|
|
|
*/
|
|
|
|
b = tpacpi_check_std_acpi_brightness_support();
|
|
|
|
switch (b) {
|
|
|
|
case 16:
|
|
|
|
bright_maxlvl = 15;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("detected a 16-level brightness capable ThinkPad\n");
|
2010-05-17 06:45:33 +08:00
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
case 0:
|
|
|
|
bright_maxlvl = 7;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("detected a 8-level brightness capable ThinkPad\n");
|
2010-05-17 06:45:33 +08:00
|
|
|
break;
|
|
|
|
default:
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Unsupported brightness interface, "
|
2010-05-17 06:45:33 +08:00
|
|
|
"please contact %s\n", TPACPI_MAIL);
|
|
|
|
tp_features.bright_unkfw = 1;
|
|
|
|
bright_maxlvl = b - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init brightness_init(struct ibm_init_struct *iibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2010-02-18 05:39:44 +08:00
|
|
|
struct backlight_properties props;
|
2007-03-24 04:33:57 +08:00
|
|
|
int b;
|
2009-08-01 23:04:20 +08:00
|
|
|
unsigned long quirks;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
|
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
mutex_init(&brightness_mutex);
|
|
|
|
|
2009-08-01 23:04:20 +08:00
|
|
|
quirks = tpacpi_check_quirks(brightness_quirk_table,
|
|
|
|
ARRAY_SIZE(brightness_quirk_table));
|
|
|
|
|
2010-05-17 06:45:33 +08:00
|
|
|
/* tpacpi_detect_brightness_capabilities() must have run already */
|
|
|
|
|
|
|
|
/* if it is unknown, we don't handle it: it wouldn't be safe */
|
|
|
|
if (tp_features.bright_unkfw)
|
|
|
|
return 1;
|
2008-08-01 23:38:03 +08:00
|
|
|
|
2008-04-26 12:02:17 +08:00
|
|
|
if (!brightness_enable) {
|
2009-04-04 12:25:53 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
|
2008-04-26 12:02:17 +08:00
|
|
|
"brightness support disabled by "
|
|
|
|
"module parameter\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-08-10 10:48:19 +08:00
|
|
|
if (acpi_video_backlight_support()) {
|
|
|
|
if (brightness_enable > 1) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("Standard ACPI backlight interface "
|
|
|
|
"available, not loading native one\n");
|
2010-08-10 10:48:19 +08:00
|
|
|
return 1;
|
|
|
|
} else if (brightness_enable == 1) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_warn("Cannot enable backlight brightness support, "
|
2010-08-10 10:48:19 +08:00
|
|
|
"ACPI is already handling it. Refer to the "
|
2011-04-05 01:06:25 +08:00
|
|
|
"acpi_backlight kernel parameter.\n");
|
2010-08-10 10:48:19 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else if (tp_features.bright_acpimode && brightness_enable > 1) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("Standard ACPI backlight interface not "
|
|
|
|
"available, thinkpad_acpi native "
|
|
|
|
"brightness control enabled\n");
|
2010-08-10 10:48:19 +08:00
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
/*
|
|
|
|
* Check for module parameter bogosity, note that we
|
|
|
|
* init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
|
|
|
|
* able to detect "unspecified"
|
|
|
|
*/
|
|
|
|
if (brightness_mode > TPACPI_BRGHT_MODE_MAX)
|
|
|
|
return -EINVAL;
|
2007-07-19 10:45:43 +08:00
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
/* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */
|
|
|
|
if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
|
|
|
|
brightness_mode == TPACPI_BRGHT_MODE_MAX) {
|
2009-08-01 23:04:20 +08:00
|
|
|
if (quirks & TPACPI_BRGHT_Q_EC)
|
|
|
|
brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
|
|
|
|
else
|
2009-04-04 12:25:53 +08:00
|
|
|
brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
|
2007-07-19 10:45:43 +08:00
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
dbg_printk(TPACPI_DBG_BRGHT,
|
2009-08-01 23:04:20 +08:00
|
|
|
"driver auto-selected brightness_mode=%d\n",
|
2009-04-04 12:25:53 +08:00
|
|
|
brightness_mode);
|
|
|
|
}
|
2007-07-19 10:45:43 +08:00
|
|
|
|
2009-06-18 11:40:16 +08:00
|
|
|
/* Safety */
|
2010-05-17 06:45:23 +08:00
|
|
|
if (!tpacpi_is_ibm() &&
|
2009-06-18 11:40:16 +08:00
|
|
|
(brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
|
|
|
|
brightness_mode == TPACPI_BRGHT_MODE_EC))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
if (tpacpi_brightness_get_raw(&b) < 0)
|
2007-07-19 10:45:43 +08:00
|
|
|
return 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2010-02-18 05:39:44 +08:00
|
|
|
memset(&props, 0, sizeof(struct backlight_properties));
|
2011-03-23 07:30:21 +08:00
|
|
|
props.type = BACKLIGHT_PLATFORM;
|
2010-05-17 06:45:33 +08:00
|
|
|
props.max_brightness = bright_maxlvl;
|
|
|
|
props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
|
2010-02-18 05:39:44 +08:00
|
|
|
ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
|
|
|
|
NULL, NULL,
|
|
|
|
&ibm_backlight_data,
|
|
|
|
&props);
|
2007-03-24 04:33:57 +08:00
|
|
|
if (IS_ERR(ibm_backlight_device)) {
|
2009-09-21 01:09:22 +08:00
|
|
|
int rc = PTR_ERR(ibm_backlight_device);
|
|
|
|
ibm_backlight_device = NULL;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Could not register backlight device\n");
|
2009-09-21 01:09:22 +08:00
|
|
|
return rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
2009-04-04 12:25:53 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
|
|
|
|
"brightness is supported\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-08-01 23:04:20 +08:00
|
|
|
if (quirks & TPACPI_BRGHT_Q_ASK) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("brightness: will use unverified default: "
|
|
|
|
"brightness_mode=%d\n", brightness_mode);
|
|
|
|
pr_notice("brightness: please report to %s whether it works well "
|
|
|
|
"or not on your ThinkPad\n", TPACPI_MAIL);
|
2009-08-01 23:04:20 +08:00
|
|
|
}
|
|
|
|
|
2010-05-17 06:45:57 +08:00
|
|
|
/* Added by mistake in early 2007. Probably useless, but it could
|
|
|
|
* be working around some unknown firmware problem where the value
|
|
|
|
* read at startup doesn't match the real hardware state... so leave
|
|
|
|
* it in place just in case */
|
2007-03-24 04:33:57 +08:00
|
|
|
backlight_update_status(ibm_backlight_device);
|
|
|
|
|
2009-12-09 09:36:24 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
|
|
|
|
"brightness: registering brightness hotkeys "
|
|
|
|
"as change notification\n");
|
|
|
|
tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
|
|
|
|
| TP_ACPI_HKEY_BRGHTUP_MASK
|
2010-11-15 11:04:38 +08:00
|
|
|
| TP_ACPI_HKEY_BRGHTDWN_MASK);
|
2007-03-24 04:33:57 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-06-28 05:18:44 +08:00
|
|
|
static void brightness_suspend(void)
|
2009-04-04 12:25:53 +08:00
|
|
|
{
|
|
|
|
tpacpi_brightness_checkpoint_nvram();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void brightness_shutdown(void)
|
|
|
|
{
|
|
|
|
tpacpi_brightness_checkpoint_nvram();
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void brightness_exit(void)
|
|
|
|
{
|
|
|
|
if (ibm_backlight_device) {
|
2009-04-04 12:25:53 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_BRGHT,
|
2007-04-21 22:08:32 +08:00
|
|
|
"calling backlight_device_unregister()\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
backlight_device_unregister(ibm_backlight_device);
|
|
|
|
}
|
2009-04-04 12:25:53 +08:00
|
|
|
|
|
|
|
tpacpi_brightness_checkpoint_nvram();
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int brightness_read(struct seq_file *m)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int level;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
level = brightness_get(NULL);
|
|
|
|
if (level < 0) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\tunreadable\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
} else {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\t%d\n", level);
|
|
|
|
seq_printf(m, "commands:\tup, down\n");
|
2010-05-17 06:45:33 +08:00
|
|
|
seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
|
|
|
|
bright_maxlvl);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2006-10-21 05:30:28 +08:00
|
|
|
static int brightness_write(char *buf)
|
|
|
|
{
|
|
|
|
int level;
|
2007-10-31 03:46:25 +08:00
|
|
|
int rc;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *cmd;
|
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
level = brightness_get(NULL);
|
|
|
|
if (level < 0)
|
|
|
|
return level;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (strlencmp(cmd, "up") == 0) {
|
2010-05-17 06:45:33 +08:00
|
|
|
if (level < bright_maxlvl)
|
2007-10-31 03:46:25 +08:00
|
|
|
level++;
|
2005-08-17 12:00:00 +08:00
|
|
|
} else if (strlencmp(cmd, "down") == 0) {
|
2007-10-31 03:46:25 +08:00
|
|
|
if (level > 0)
|
|
|
|
level--;
|
|
|
|
} else if (sscanf(cmd, "level %d", &level) == 1 &&
|
2010-05-17 06:45:33 +08:00
|
|
|
level >= 0 && level <= bright_maxlvl) {
|
2007-10-31 03:46:25 +08:00
|
|
|
/* new level set */
|
2005-08-17 12:00:00 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:53 +08:00
|
|
|
tpacpi_disclose_usertask("procfs brightness",
|
|
|
|
"set level to %d\n", level);
|
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
2009-12-09 09:36:24 +08:00
|
|
|
if (!rc && ibm_backlight_device)
|
|
|
|
backlight_force_update(ibm_backlight_device,
|
|
|
|
BACKLIGHT_UPDATE_SYSFS);
|
2009-11-21 02:48:23 +08:00
|
|
|
return (rc == -EINTR)? -ERESTARTSYS : rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct brightness_driver_data = {
|
|
|
|
.name = "brightness",
|
|
|
|
.read = brightness_read,
|
|
|
|
.write = brightness_write,
|
|
|
|
.exit = brightness_exit,
|
2009-04-04 12:25:53 +08:00
|
|
|
.suspend = brightness_suspend,
|
|
|
|
.shutdown = brightness_shutdown,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Volume subdriver
|
|
|
|
*/
|
2006-11-26 02:35:09 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
/*
|
|
|
|
* IBM ThinkPads have a simple volume controller with MUTE gating.
|
|
|
|
* Very early Lenovo ThinkPads follow the IBM ThinkPad spec.
|
|
|
|
*
|
|
|
|
* Since the *61 series (and probably also the later *60 series), Lenovo
|
|
|
|
* ThinkPads only implement the MUTE gate.
|
|
|
|
*
|
|
|
|
* EC register 0x30
|
|
|
|
* Bit 6: MUTE (1 mutes sound)
|
|
|
|
* Bit 3-0: Volume
|
|
|
|
* Other bits should be zero as far as we know.
|
|
|
|
*
|
|
|
|
* This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and
|
|
|
|
* bits 3-0 (volume). Other bits in NVRAM may have other functions,
|
|
|
|
* such as bit 7 which is used to detect repeated presses of MUTE,
|
|
|
|
* and we leave them unchanged.
|
|
|
|
*/
|
|
|
|
|
2009-12-27 08:52:15 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
#define TPACPI_ALSA_DRVNAME "ThinkPad EC"
|
|
|
|
#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
|
|
|
|
#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
|
|
|
|
|
2009-12-27 08:52:13 +08:00
|
|
|
static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */
|
2009-12-16 07:51:11 +08:00
|
|
|
static char *alsa_id = "ThinkPadEC";
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool alsa_enable = SNDRV_DEFAULT_ENABLE1;
|
2009-12-16 07:51:11 +08:00
|
|
|
|
|
|
|
struct tpacpi_alsa_data {
|
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_ctl_elem_id *ctl_mute_id;
|
|
|
|
struct snd_ctl_elem_id *ctl_vol_id;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct snd_card *alsa_card;
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
enum {
|
|
|
|
TP_EC_AUDIO = 0x30,
|
|
|
|
|
|
|
|
/* TP_EC_AUDIO bits */
|
|
|
|
TP_EC_AUDIO_MUTESW = 6,
|
|
|
|
|
|
|
|
/* TP_EC_AUDIO bitmasks */
|
|
|
|
TP_EC_AUDIO_LVL_MSK = 0x0F,
|
|
|
|
TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW),
|
|
|
|
|
|
|
|
/* Maximum volume */
|
|
|
|
TP_EC_VOLUME_MAX = 14,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum tpacpi_volume_access_mode {
|
|
|
|
TPACPI_VOL_MODE_AUTO = 0, /* Not implemented yet */
|
|
|
|
TPACPI_VOL_MODE_EC, /* Pure EC control */
|
|
|
|
TPACPI_VOL_MODE_UCMS_STEP, /* UCMS step-based control: N/A */
|
|
|
|
TPACPI_VOL_MODE_ECNVRAM, /* EC control w/ NVRAM store */
|
|
|
|
TPACPI_VOL_MODE_MAX
|
|
|
|
};
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
enum tpacpi_volume_capabilities {
|
|
|
|
TPACPI_VOL_CAP_AUTO = 0, /* Use white/blacklist */
|
|
|
|
TPACPI_VOL_CAP_VOLMUTE, /* Output vol and mute */
|
|
|
|
TPACPI_VOL_CAP_MUTEONLY, /* Output mute only */
|
|
|
|
TPACPI_VOL_CAP_MAX
|
|
|
|
};
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static enum tpacpi_volume_access_mode volume_mode =
|
|
|
|
TPACPI_VOL_MODE_MAX;
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
static enum tpacpi_volume_capabilities volume_capabilities;
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool volume_control_allowed;
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
/*
|
|
|
|
* Used to syncronize writers to TP_EC_AUDIO and
|
|
|
|
* TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
|
|
|
|
*/
|
|
|
|
static struct mutex volume_mutex;
|
|
|
|
|
|
|
|
static void tpacpi_volume_checkpoint_nvram(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2009-12-16 07:51:08 +08:00
|
|
|
u8 lec = 0;
|
|
|
|
u8 b_nvram;
|
2009-12-16 07:51:09 +08:00
|
|
|
u8 ec_mask;
|
2009-12-16 07:51:08 +08:00
|
|
|
|
|
|
|
if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
|
|
|
|
return;
|
2009-12-16 07:51:10 +08:00
|
|
|
if (!volume_control_allowed)
|
|
|
|
return;
|
2009-12-16 07:51:08 +08:00
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_MIXER,
|
|
|
|
"trying to checkpoint mixer state to NVRAM...\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
if (tp_features.mixer_no_level_control)
|
|
|
|
ec_mask = TP_EC_AUDIO_MUTESW_MSK;
|
|
|
|
else
|
|
|
|
ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
if (mutex_lock_killable(&volume_mutex) < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
|
|
|
|
goto unlock;
|
|
|
|
lec &= ec_mask;
|
|
|
|
b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
|
|
|
|
|
|
|
|
if (lec != (b_nvram & ec_mask)) {
|
|
|
|
/* NVRAM needs update */
|
|
|
|
b_nvram &= ~ec_mask;
|
|
|
|
b_nvram |= lec;
|
|
|
|
nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER);
|
|
|
|
dbg_printk(TPACPI_DBG_MIXER,
|
|
|
|
"updated NVRAM mixer status to 0x%02x (0x%02x)\n",
|
|
|
|
(unsigned int) lec, (unsigned int) b_nvram);
|
2005-08-17 12:00:00 +08:00
|
|
|
} else {
|
2009-12-16 07:51:08 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_MIXER,
|
|
|
|
"NVRAM mixer status already is 0x%02x (0x%02x)\n",
|
|
|
|
(unsigned int) lec, (unsigned int) b_nvram);
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
unlock:
|
|
|
|
mutex_unlock(&volume_mutex);
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static int volume_get_status_ec(u8 *status)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2009-12-16 07:51:08 +08:00
|
|
|
u8 s;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
if (!acpi_ec_read(TP_EC_AUDIO, &s))
|
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
*status = s;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
|
2008-01-08 23:02:49 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static int volume_get_status(u8 *status)
|
|
|
|
{
|
|
|
|
return volume_get_status_ec(status);
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static int volume_set_status_ec(const u8 status)
|
|
|
|
{
|
|
|
|
if (!acpi_ec_write(TP_EC_AUDIO, status))
|
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_set_status(const u8 status)
|
|
|
|
{
|
|
|
|
return volume_set_status_ec(status);
|
|
|
|
}
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
/* returns < 0 on error, 0 on no change, 1 on change */
|
|
|
|
static int __volume_set_mute_ec(const bool mute)
|
2009-12-16 07:51:08 +08:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 s, n;
|
|
|
|
|
|
|
|
if (mutex_lock_killable(&volume_mutex) < 0)
|
|
|
|
return -EINTR;
|
|
|
|
|
|
|
|
rc = volume_get_status_ec(&s);
|
|
|
|
if (rc)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
|
|
|
|
s & ~TP_EC_AUDIO_MUTESW_MSK;
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
if (n != s) {
|
2009-12-16 07:51:08 +08:00
|
|
|
rc = volume_set_status_ec(n);
|
2010-02-28 05:45:29 +08:00
|
|
|
if (!rc)
|
|
|
|
rc = 1;
|
|
|
|
}
|
2009-12-16 07:51:08 +08:00
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&volume_mutex);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
static int volume_alsa_set_mute(const bool mute)
|
|
|
|
{
|
|
|
|
dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n",
|
|
|
|
(mute) ? "" : "un");
|
|
|
|
return __volume_set_mute_ec(mute);
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static int volume_set_mute(const bool mute)
|
|
|
|
{
|
2010-02-28 05:45:29 +08:00
|
|
|
int rc;
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
|
|
|
|
(mute) ? "" : "un");
|
2010-02-28 05:45:29 +08:00
|
|
|
|
|
|
|
rc = __volume_set_mute_ec(mute);
|
|
|
|
return (rc < 0) ? rc : 0;
|
2009-12-16 07:51:08 +08:00
|
|
|
}
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
/* returns < 0 on error, 0 on no change, 1 on change */
|
|
|
|
static int __volume_set_volume_ec(const u8 vol)
|
2009-12-16 07:51:08 +08:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 s, n;
|
|
|
|
|
|
|
|
if (vol > TP_EC_VOLUME_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mutex_lock_killable(&volume_mutex) < 0)
|
|
|
|
return -EINTR;
|
|
|
|
|
|
|
|
rc = volume_get_status_ec(&s);
|
|
|
|
if (rc)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
if (n != s) {
|
2009-12-16 07:51:08 +08:00
|
|
|
rc = volume_set_status_ec(n);
|
2010-02-28 05:45:29 +08:00
|
|
|
if (!rc)
|
|
|
|
rc = 1;
|
|
|
|
}
|
2009-12-16 07:51:08 +08:00
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&volume_mutex);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-02-28 05:45:29 +08:00
|
|
|
static int volume_alsa_set_volume(const u8 vol)
|
2009-12-16 07:51:08 +08:00
|
|
|
{
|
|
|
|
dbg_printk(TPACPI_DBG_MIXER,
|
2010-02-28 05:45:29 +08:00
|
|
|
"ALSA: trying to set volume level to %hu\n", vol);
|
|
|
|
return __volume_set_volume_ec(vol);
|
2009-12-16 07:51:08 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
static void volume_alsa_notify_change(void)
|
|
|
|
{
|
|
|
|
struct tpacpi_alsa_data *d;
|
|
|
|
|
|
|
|
if (alsa_card && alsa_card->private_data) {
|
|
|
|
d = alsa_card->private_data;
|
|
|
|
if (d->ctl_mute_id)
|
|
|
|
snd_ctl_notify(alsa_card,
|
|
|
|
SNDRV_CTL_EVENT_MASK_VALUE,
|
|
|
|
d->ctl_mute_id);
|
|
|
|
if (d->ctl_vol_id)
|
|
|
|
snd_ctl_notify(alsa_card,
|
|
|
|
SNDRV_CTL_EVENT_MASK_VALUE,
|
|
|
|
d->ctl_vol_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = TP_EC_VOLUME_MAX;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
u8 s;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = volume_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2010-05-17 06:45:40 +08:00
|
|
|
tpacpi_disclose_usertask("ALSA", "set volume to %ld\n",
|
|
|
|
ucontrol->value.integer.value[0]);
|
2010-02-28 05:45:29 +08:00
|
|
|
return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
|
2009-12-16 07:51:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#define volume_alsa_mute_info snd_ctl_boolean_mono_info
|
|
|
|
|
|
|
|
static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
u8 s;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = volume_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
|
|
(s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2010-05-17 06:45:40 +08:00
|
|
|
tpacpi_disclose_usertask("ALSA", "%smute\n",
|
|
|
|
ucontrol->value.integer.value[0] ?
|
|
|
|
"un" : "");
|
2010-02-28 05:45:29 +08:00
|
|
|
return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
|
2009-12-16 07:51:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Console Playback Volume",
|
|
|
|
.index = 0,
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
|
|
.info = volume_alsa_vol_info,
|
|
|
|
.get = volume_alsa_vol_get,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = {
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Console Playback Switch",
|
|
|
|
.index = 0,
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
|
|
.info = volume_alsa_mute_info,
|
|
|
|
.get = volume_alsa_mute_get,
|
|
|
|
};
|
|
|
|
|
2012-06-28 05:18:44 +08:00
|
|
|
static void volume_suspend(void)
|
2009-12-16 07:51:08 +08:00
|
|
|
{
|
|
|
|
tpacpi_volume_checkpoint_nvram();
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
static void volume_resume(void)
|
|
|
|
{
|
|
|
|
volume_alsa_notify_change();
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static void volume_shutdown(void)
|
|
|
|
{
|
|
|
|
tpacpi_volume_checkpoint_nvram();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void volume_exit(void)
|
|
|
|
{
|
2009-12-16 07:51:11 +08:00
|
|
|
if (alsa_card) {
|
|
|
|
snd_card_free(alsa_card);
|
|
|
|
alsa_card = NULL;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
tpacpi_volume_checkpoint_nvram();
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
static int __init volume_create_alsa_mixer(void)
|
|
|
|
{
|
|
|
|
struct snd_card *card;
|
|
|
|
struct tpacpi_alsa_data *data;
|
|
|
|
struct snd_kcontrol *ctl_vol;
|
|
|
|
struct snd_kcontrol *ctl_mute;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE,
|
|
|
|
sizeof(struct tpacpi_alsa_data), &card);
|
2009-12-27 08:52:14 +08:00
|
|
|
if (rc < 0 || !card) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Failed to create ALSA card structures: %d\n", rc);
|
2009-12-27 08:52:14 +08:00
|
|
|
return 1;
|
|
|
|
}
|
2009-12-16 07:51:11 +08:00
|
|
|
|
|
|
|
BUG_ON(!card->private_data);
|
|
|
|
data = card->private_data;
|
|
|
|
data->card = card;
|
|
|
|
|
|
|
|
strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
|
|
|
|
sizeof(card->driver));
|
|
|
|
strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
|
|
|
|
sizeof(card->shortname));
|
|
|
|
snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
|
|
|
|
(thinkpad_id.ec_version_str) ?
|
|
|
|
thinkpad_id.ec_version_str : "(unknown)");
|
|
|
|
snprintf(card->longname, sizeof(card->longname),
|
|
|
|
"%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
|
|
|
|
(thinkpad_id.ec_version_str) ?
|
|
|
|
thinkpad_id.ec_version_str : "unknown");
|
|
|
|
|
|
|
|
if (volume_control_allowed) {
|
|
|
|
volume_alsa_control_vol.put = volume_alsa_vol_put;
|
|
|
|
volume_alsa_control_vol.access =
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
|
|
|
|
volume_alsa_control_mute.put = volume_alsa_mute_put;
|
|
|
|
volume_alsa_control_mute.access =
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tp_features.mixer_no_level_control) {
|
|
|
|
ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
|
|
|
|
rc = snd_ctl_add(card, ctl_vol);
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Failed to create ALSA volume control: %d\n",
|
|
|
|
rc);
|
2009-12-27 08:52:14 +08:00
|
|
|
goto err_exit;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2009-12-16 07:51:11 +08:00
|
|
|
data->ctl_vol_id = &ctl_vol->id;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
|
|
|
|
rc = snd_ctl_add(card, ctl_mute);
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Failed to create ALSA mute control: %d\n", rc);
|
2009-12-27 08:52:14 +08:00
|
|
|
goto err_exit;
|
2009-12-16 07:51:11 +08:00
|
|
|
}
|
|
|
|
data->ctl_mute_id = &ctl_mute->id;
|
2008-01-08 23:02:49 +08:00
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
snd_card_set_dev(card, &tpacpi_pdev->dev);
|
|
|
|
rc = snd_card_register(card);
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Failed to register ALSA card: %d\n", rc);
|
2009-12-27 08:52:14 +08:00
|
|
|
goto err_exit;
|
2009-12-16 07:51:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
alsa_card = card;
|
2009-12-27 08:52:14 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_exit:
|
|
|
|
snd_card_free(card);
|
|
|
|
return 1;
|
2009-12-16 07:51:11 +08:00
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
|
|
|
|
#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */
|
|
|
|
|
|
|
|
static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
|
|
|
|
/* Whitelist volume level on all IBM by default */
|
|
|
|
{ .vendor = PCI_VENDOR_ID_IBM,
|
|
|
|
.bios = TPACPI_MATCH_ANY,
|
|
|
|
.ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = TPACPI_VOL_Q_LEVEL },
|
|
|
|
|
|
|
|
/* Lenovo models with volume control (needs confirmation) */
|
|
|
|
TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
|
|
|
|
TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
|
|
|
|
TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
|
|
|
|
TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
|
|
|
|
TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
|
|
|
|
TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
|
|
|
|
TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
|
|
|
|
|
|
|
|
/* Whitelist mute-only on all Lenovo by default */
|
|
|
|
{ .vendor = PCI_VENDOR_ID_LENOVO,
|
|
|
|
.bios = TPACPI_MATCH_ANY,
|
|
|
|
.ec = TPACPI_MATCH_ANY,
|
|
|
|
.quirks = TPACPI_VOL_Q_MUTEONLY }
|
|
|
|
};
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
static int __init volume_init(struct ibm_init_struct *iibm)
|
|
|
|
{
|
2009-12-16 07:51:09 +08:00
|
|
|
unsigned long quirks;
|
2009-12-16 07:51:11 +08:00
|
|
|
int rc;
|
2009-12-16 07:51:09 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
|
|
|
|
|
|
|
|
mutex_init(&volume_mutex);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for module parameter bogosity, note that we
|
|
|
|
* init volume_mode to TPACPI_VOL_MODE_MAX in order to be
|
|
|
|
* able to detect "unspecified"
|
|
|
|
*/
|
|
|
|
if (volume_mode > TPACPI_VOL_MODE_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("UCMS step volume mode not implemented, "
|
|
|
|
"please contact %s\n", TPACPI_MAIL);
|
2009-12-16 07:51:08 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
/*
|
|
|
|
* The ALSA mixer is our primary interface.
|
|
|
|
* When disabled, don't install the subdriver at all
|
|
|
|
*/
|
|
|
|
if (!alsa_enable) {
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
|
"ALSA mixer disabled by parameter, "
|
|
|
|
"not loading volume subdriver...\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
quirks = tpacpi_check_quirks(volume_quirk_table,
|
|
|
|
ARRAY_SIZE(volume_quirk_table));
|
|
|
|
|
|
|
|
switch (volume_capabilities) {
|
|
|
|
case TPACPI_VOL_CAP_AUTO:
|
|
|
|
if (quirks & TPACPI_VOL_Q_MUTEONLY)
|
|
|
|
tp_features.mixer_no_level_control = 1;
|
|
|
|
else if (quirks & TPACPI_VOL_Q_LEVEL)
|
|
|
|
tp_features.mixer_no_level_control = 0;
|
|
|
|
else
|
|
|
|
return 1; /* no mixer */
|
|
|
|
break;
|
|
|
|
case TPACPI_VOL_CAP_VOLMUTE:
|
|
|
|
tp_features.mixer_no_level_control = 0;
|
|
|
|
break;
|
|
|
|
case TPACPI_VOL_CAP_MUTEONLY:
|
|
|
|
tp_features.mixer_no_level_control = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
|
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
|
"using user-supplied volume_capabilities=%d\n",
|
|
|
|
volume_capabilities);
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
if (volume_mode == TPACPI_VOL_MODE_AUTO ||
|
|
|
|
volume_mode == TPACPI_VOL_MODE_MAX) {
|
|
|
|
volume_mode = TPACPI_VOL_MODE_ECNVRAM;
|
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
|
"driver auto-selected volume_mode=%d\n",
|
|
|
|
volume_mode);
|
|
|
|
} else {
|
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
|
"using user-supplied volume_mode=%d\n",
|
|
|
|
volume_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
2009-12-16 07:51:09 +08:00
|
|
|
"mute is supported, volume control is %s\n",
|
|
|
|
str_supported(!tp_features.mixer_no_level_control));
|
2009-12-16 07:51:08 +08:00
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
rc = volume_create_alsa_mixer();
|
|
|
|
if (rc) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Could not create the ALSA mixer interface\n");
|
2009-12-16 07:51:11 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("Console audio control enabled, mode: %s\n",
|
2009-12-16 07:51:10 +08:00
|
|
|
(volume_control_allowed) ?
|
|
|
|
"override (read/write)" :
|
|
|
|
"monitor (read only)");
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
|
|
|
|
"registering volume hotkeys as change notification\n");
|
|
|
|
tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
|
|
|
|
| TP_ACPI_HKEY_VOLUP_MASK
|
|
|
|
| TP_ACPI_HKEY_VOLDWN_MASK
|
|
|
|
| TP_ACPI_HKEY_MUTE_MASK);
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int volume_read(struct seq_file *m)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2009-12-16 07:51:08 +08:00
|
|
|
u8 status;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
if (volume_get_status(&status) < 0) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\tunreadable\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
} else {
|
2009-12-16 07:51:09 +08:00
|
|
|
if (tp_features.mixer_no_level_control)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\tunsupported\n");
|
2009-12-16 07:51:09 +08:00
|
|
|
else
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\t%d\n",
|
2009-12-16 07:51:09 +08:00
|
|
|
status & TP_EC_AUDIO_LVL_MSK);
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "mute:\t\t%s\n",
|
2009-12-16 07:51:08 +08:00
|
|
|
onoff(status, TP_EC_AUDIO_MUTESW));
|
2009-12-16 07:51:09 +08:00
|
|
|
|
2009-12-16 07:51:10 +08:00
|
|
|
if (volume_control_allowed) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tunmute, mute\n");
|
2009-12-16 07:51:10 +08:00
|
|
|
if (!tp_features.mixer_no_level_control) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m,
|
2009-12-16 07:51:10 +08:00
|
|
|
"commands:\tup, down\n");
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m,
|
2009-12-16 07:51:10 +08:00
|
|
|
"commands:\tlevel <level>"
|
|
|
|
" (<level> is 0-%d)\n",
|
|
|
|
TP_EC_VOLUME_MAX);
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_write(char *buf)
|
|
|
|
{
|
2009-12-16 07:51:08 +08:00
|
|
|
u8 s;
|
|
|
|
u8 new_level, new_mute;
|
|
|
|
int l;
|
2005-08-17 12:00:00 +08:00
|
|
|
char *cmd;
|
2009-12-16 07:51:08 +08:00
|
|
|
int rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:10 +08:00
|
|
|
/*
|
|
|
|
* We do allow volume control at driver startup, so that the
|
|
|
|
* user can set initial state through the volume=... parameter hack.
|
|
|
|
*/
|
|
|
|
if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) {
|
|
|
|
if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
|
|
|
|
tp_warned.volume_ctrl_forbidden = 1;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("Console audio control in monitor mode, "
|
|
|
|
"changes are not allowed\n");
|
|
|
|
pr_notice("Use the volume_control=1 module parameter "
|
|
|
|
"to enable volume control\n");
|
2009-12-16 07:51:10 +08:00
|
|
|
}
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
rc = volume_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
new_level = s & TP_EC_AUDIO_LVL_MSK;
|
|
|
|
new_mute = s & TP_EC_AUDIO_MUTESW_MSK;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2009-12-16 07:51:09 +08:00
|
|
|
if (!tp_features.mixer_no_level_control) {
|
|
|
|
if (strlencmp(cmd, "up") == 0) {
|
|
|
|
if (new_mute)
|
|
|
|
new_mute = 0;
|
|
|
|
else if (new_level < TP_EC_VOLUME_MAX)
|
|
|
|
new_level++;
|
|
|
|
continue;
|
|
|
|
} else if (strlencmp(cmd, "down") == 0) {
|
|
|
|
if (new_mute)
|
|
|
|
new_mute = 0;
|
|
|
|
else if (new_level > 0)
|
|
|
|
new_level--;
|
|
|
|
continue;
|
|
|
|
} else if (sscanf(cmd, "level %u", &l) == 1 &&
|
|
|
|
l >= 0 && l <= TP_EC_VOLUME_MAX) {
|
|
|
|
new_level = l;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (strlencmp(cmd, "mute") == 0)
|
2009-12-16 07:51:08 +08:00
|
|
|
new_mute = TP_EC_AUDIO_MUTESW_MSK;
|
2009-12-16 07:51:09 +08:00
|
|
|
else if (strlencmp(cmd, "unmute") == 0)
|
|
|
|
new_mute = 0;
|
|
|
|
else
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2009-12-16 07:51:08 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
if (tp_features.mixer_no_level_control) {
|
|
|
|
tpacpi_disclose_usertask("procfs volume", "%smute\n",
|
|
|
|
new_mute ? "" : "un");
|
|
|
|
rc = volume_set_mute(!!new_mute);
|
|
|
|
} else {
|
|
|
|
tpacpi_disclose_usertask("procfs volume",
|
|
|
|
"%smute and set level to %d\n",
|
|
|
|
new_mute ? "" : "un", new_level);
|
|
|
|
rc = volume_set_status(new_mute | new_level);
|
|
|
|
}
|
2009-12-16 07:51:11 +08:00
|
|
|
volume_alsa_notify_change();
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-12-16 07:51:08 +08:00
|
|
|
return (rc == -EINTR) ? -ERESTARTSYS : rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct volume_driver_data = {
|
|
|
|
.name = "volume",
|
|
|
|
.read = volume_read,
|
|
|
|
.write = volume_write,
|
2009-12-16 07:51:08 +08:00
|
|
|
.exit = volume_exit,
|
|
|
|
.suspend = volume_suspend,
|
2009-12-16 07:51:11 +08:00
|
|
|
.resume = volume_resume,
|
2009-12-16 07:51:08 +08:00
|
|
|
.shutdown = volume_shutdown,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2009-12-27 08:52:15 +08:00
|
|
|
#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
|
|
|
|
|
|
|
|
#define alsa_card NULL
|
|
|
|
|
|
|
|
static void inline volume_alsa_notify_change(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init volume_init(struct ibm_init_struct *iibm)
|
|
|
|
{
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("volume: disabled as there is no ALSA support in this kernel\n");
|
2009-12-27 08:52:15 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ibm_struct volume_driver_data = {
|
|
|
|
.name = "volume",
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Fan subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FAN ACCESS MODES
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_RD_ACPI_GFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ACPI GFAN method: returns fan level
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* see TPACPI_FAN_WR_ACPI_SFAN
|
2007-04-21 22:08:29 +08:00
|
|
|
* EC 0x2f (HFSP) not available if GFAN exists
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_SFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
|
|
|
|
*
|
2007-04-21 22:08:29 +08:00
|
|
|
* EC 0x2f (HFSP) might be available *for reading*, but do not use
|
|
|
|
* it for writing.
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_TPEC:
|
2007-04-21 22:08:29 +08:00
|
|
|
* ThinkPad EC register 0x2f (HFSP): fan control loop mode
|
|
|
|
* Supported on almost all ThinkPads
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* Fan speed changes of any sort (including those caused by the
|
|
|
|
* disengaged mode) are usually done slowly by the firmware as the
|
2010-01-21 00:02:24 +08:00
|
|
|
* maximum amount of fan duty cycle change per second seems to be
|
2007-03-24 04:33:57 +08:00
|
|
|
* limited.
|
|
|
|
*
|
|
|
|
* Reading is not available if GFAN exists.
|
|
|
|
* Writing is not available if SFAN exists.
|
|
|
|
*
|
|
|
|
* Bits
|
|
|
|
* 7 automatic mode engaged;
|
|
|
|
* (default operation mode of the ThinkPad)
|
|
|
|
* fan level is ignored in this mode.
|
2007-04-21 22:08:29 +08:00
|
|
|
* 6 full speed mode (takes precedence over bit 7);
|
2007-03-24 04:33:57 +08:00
|
|
|
* not available on all thinkpads. May disable
|
2007-04-21 22:08:29 +08:00
|
|
|
* the tachometer while the fan controller ramps up
|
|
|
|
* the speed (which can take up to a few *minutes*).
|
|
|
|
* Speeds up fan to 100% duty-cycle, which is far above
|
|
|
|
* the standard RPM levels. It is not impossible that
|
|
|
|
* it could cause hardware damage.
|
2007-03-24 04:33:57 +08:00
|
|
|
* 5-3 unused in some models. Extra bits for fan level
|
|
|
|
* in others, but still useless as all values above
|
|
|
|
* 7 map to the same speed as level 7 in these models.
|
|
|
|
* 2-0 fan level (0..7 usually)
|
|
|
|
* 0x00 = stop
|
|
|
|
* 0x07 = max (set when temperatures critical)
|
|
|
|
* Some ThinkPads may have other levels, see
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
|
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-02 03:38:34 +08:00
|
|
|
* boot. Apparently the EC does not initialize it, so unless ACPI DSDT
|
2007-03-24 04:33:57 +08:00
|
|
|
* does so, its initial value is meaningless (0x07).
|
|
|
|
*
|
|
|
|
* For firmware bugs, refer to:
|
|
|
|
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
|
|
|
|
*
|
|
|
|
* ----
|
|
|
|
*
|
|
|
|
* ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
|
|
|
|
* Main fan tachometer reading (in RPM)
|
|
|
|
*
|
|
|
|
* This register is present on all ThinkPads with a new-style EC, and
|
|
|
|
* it is known not to be present on the A21m/e, and T22, as there is
|
|
|
|
* something else in offset 0x84 according to the ACPI DSDT. Other
|
|
|
|
* ThinkPads from this same time period (and earlier) probably lack the
|
|
|
|
* tachometer as well.
|
|
|
|
*
|
2009-01-26 18:06:57 +08:00
|
|
|
* Unfortunately a lot of ThinkPads with new-style ECs but whose firmware
|
2007-03-24 04:33:57 +08:00
|
|
|
* was never fixed by IBM to report the EC firmware version string
|
|
|
|
* probably support the tachometer (like the early X models), so
|
|
|
|
* detecting it is quite hard. We need more data to know for sure.
|
|
|
|
*
|
|
|
|
* FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
|
|
|
|
* might result.
|
|
|
|
*
|
2007-04-21 22:08:29 +08:00
|
|
|
* FIRMWARE BUG: may go stale while the EC is switching to full speed
|
|
|
|
* mode.
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* For firmware bugs, refer to:
|
|
|
|
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
|
|
|
|
*
|
2009-06-18 11:40:17 +08:00
|
|
|
* ----
|
|
|
|
*
|
|
|
|
* ThinkPad EC register 0x31 bit 0 (only on select models)
|
|
|
|
*
|
|
|
|
* When bit 0 of EC register 0x31 is zero, the tachometer registers
|
|
|
|
* show the speed of the main fan. When bit 0 of EC register 0x31
|
|
|
|
* is one, the tachometer registers show the speed of the auxiliary
|
|
|
|
* fan.
|
|
|
|
*
|
|
|
|
* Fan control seems to affect both fans, regardless of the state
|
|
|
|
* of this bit.
|
|
|
|
*
|
|
|
|
* So far, only the firmware for the X60/X61 non-tablet versions
|
|
|
|
* seem to support this (firmware TP-7M).
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_FANS:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ThinkPad X31, X40, X41. Not available in the X60.
|
|
|
|
*
|
|
|
|
* FANS ACPI handle: takes three arguments: low speed, medium speed,
|
|
|
|
* high speed. ACPI DSDT seems to map these three speeds to levels
|
|
|
|
* as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
|
|
|
|
* (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
|
|
|
|
*
|
|
|
|
* The speeds are stored on handles
|
|
|
|
* (FANA:FAN9), (FANC:FANB), (FANE:FAND).
|
|
|
|
*
|
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 23:09:05 +08:00
|
|
|
* There are three default speed sets, accessible as handles:
|
2007-03-24 04:33:57 +08:00
|
|
|
* FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
|
|
|
|
*
|
|
|
|
* ACPI DSDT switches which set is in use depending on various
|
|
|
|
* factors.
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_TPEC is also available and should be used to
|
2007-03-24 04:33:57 +08:00
|
|
|
* command the fan. The X31/X40/X41 seems to have 8 fan levels,
|
|
|
|
* but the ACPI tables just mention level 7.
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
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 */
|
2009-06-18 11:40:17 +08:00
|
|
|
fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M)
|
|
|
|
bit 0 selects which fan is active */
|
2008-01-08 23:02:44 +08:00
|
|
|
|
|
|
|
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 */
|
|
|
|
};
|
|
|
|
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool fan_control_allowed;
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2006-11-24 21:47:09 +08:00
|
|
|
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;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
static u8 fan_control_initial_status;
|
2007-04-24 22:48:17 +08:00
|
|
|
static u8 fan_control_desired_level;
|
2008-11-09 20:54:02 +08:00
|
|
|
static u8 fan_control_resume_level;
|
2008-01-08 23:02:44 +08:00
|
|
|
static int fan_watchdog_maxinterval;
|
|
|
|
|
|
|
|
static struct mutex fan_mutex;
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
|
2006-12-08 17:43:41 +08:00
|
|
|
static void fan_watchdog_fire(struct work_struct *ignored);
|
|
|
|
static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
|
|
|
|
TPACPI_HANDLE(gfan, ec, "GFAN", /* 570 */
|
2007-03-24 04:33:57 +08:00
|
|
|
"\\FSPD", /* 600e/x, 770e, 770x */
|
|
|
|
); /* all others */
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */
|
2007-03-24 04:33:57 +08:00
|
|
|
"JFNS", /* 770x-JL */
|
|
|
|
); /* all others */
|
|
|
|
|
2009-01-11 13:01:08 +08:00
|
|
|
/*
|
|
|
|
* Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the
|
|
|
|
* HFSP register at boot, so it contains 0x07 but the Thinkpad could
|
|
|
|
* be in auto mode (0x80).
|
|
|
|
*
|
|
|
|
* This is corrected by any write to HFSP either by the driver, or
|
|
|
|
* by the firmware.
|
|
|
|
*
|
|
|
|
* We assume 0x07 really means auto mode while this quirk is active,
|
|
|
|
* as this is far more likely than the ThinkPad being in level 7,
|
|
|
|
* which is only used by the firmware during thermal emergencies.
|
2009-05-31 00:25:06 +08:00
|
|
|
*
|
|
|
|
* Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52),
|
|
|
|
* TP-70 (T43, R52), which are known to be buggy.
|
2009-01-11 13:01:08 +08:00
|
|
|
*/
|
|
|
|
|
2009-05-31 00:25:06 +08:00
|
|
|
static void fan_quirk1_setup(void)
|
2009-01-11 13:01:08 +08:00
|
|
|
{
|
|
|
|
if (fan_control_initial_status == 0x07) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("fan_init: initial fan status is unknown, "
|
|
|
|
"assuming it is in auto mode\n");
|
2009-05-31 00:25:06 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 1;
|
2009-01-11 13:01:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fan_quirk1_handle(u8 *fan_status)
|
|
|
|
{
|
|
|
|
if (unlikely(tp_features.fan_ctrl_status_undef)) {
|
|
|
|
if (*fan_status != fan_control_initial_status) {
|
|
|
|
/* something changed the HFSP regisnter since
|
|
|
|
* driver init time, so it is not undefined
|
|
|
|
* anymore */
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
} else {
|
|
|
|
/* Return most likely status. In fact, it
|
|
|
|
* might be the only possible status */
|
|
|
|
*fan_status = TP_EC_FAN_AUTO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
/* Select main fan on X60/X61, NOOP on others */
|
|
|
|
static bool fan_select_fan1(void)
|
|
|
|
{
|
|
|
|
if (tp_features.second_fan) {
|
|
|
|
u8 val;
|
|
|
|
|
|
|
|
if (ec_read(fan_select_offset, &val) < 0)
|
|
|
|
return false;
|
|
|
|
val &= 0xFEU;
|
|
|
|
if (ec_write(fan_select_offset, val) < 0)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Select secondary fan on X60/X61 */
|
|
|
|
static bool fan_select_fan2(void)
|
|
|
|
{
|
|
|
|
u8 val;
|
|
|
|
|
|
|
|
if (!tp_features.second_fan)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (ec_read(fan_select_offset, &val) < 0)
|
|
|
|
return false;
|
|
|
|
val |= 0x01U;
|
|
|
|
if (ec_write(fan_select_offset, val) < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
/*
|
2008-01-08 23:02:45 +08:00
|
|
|
* Call with fan_mutex held
|
2007-04-24 22:48:17 +08:00
|
|
|
*/
|
2008-01-08 23:02:45 +08:00
|
|
|
static void fan_update_desired_level(u8 status)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_get_status(u8 *status)
|
|
|
|
{
|
|
|
|
u8 s;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* TODO:
|
|
|
|
* Add TPACPI_FAN_RD_ACPI_FANS ? */
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
switch (fan_status_access_mode) {
|
|
|
|
case TPACPI_FAN_RD_ACPI_GFAN:
|
|
|
|
/* 570, 600e/x, 770e, 770x */
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
|
|
|
|
return -EIO;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (likely(status))
|
|
|
|
*status = s & 0x07;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case TPACPI_FAN_RD_TPEC:
|
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
|
|
|
if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
|
|
|
|
return -EIO;
|
|
|
|
|
2009-01-11 13:01:08 +08:00
|
|
|
if (likely(status)) {
|
2008-01-08 23:02:45 +08:00
|
|
|
*status = s;
|
2009-01-11 13:01:08 +08:00
|
|
|
fan_quirk1_handle(status);
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
|
|
|
|
break;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
default:
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ENXIO;
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_get_status_safe(u8 *status)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 s;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ERESTARTSYS;
|
|
|
|
rc = fan_get_status(&s);
|
|
|
|
if (!rc)
|
|
|
|
fan_update_desired_level(s);
|
|
|
|
mutex_unlock(&fan_mutex);
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (status)
|
|
|
|
*status = s;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_get_speed(unsigned int *speed)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
u8 hi, lo;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
switch (fan_status_access_mode) {
|
|
|
|
case TPACPI_FAN_RD_TPEC:
|
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
2009-06-18 11:40:17 +08:00
|
|
|
if (unlikely(!fan_select_fan1()))
|
|
|
|
return -EIO;
|
2008-01-08 23:02:45 +08:00
|
|
|
if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
|
|
|
|
!acpi_ec_read(fan_rpm_offset + 1, &hi)))
|
|
|
|
return -EIO;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (likely(speed))
|
|
|
|
*speed = (hi << 8) | lo;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
break;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return 0;
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
static int fan2_get_speed(unsigned int *speed)
|
|
|
|
{
|
|
|
|
u8 hi, lo;
|
|
|
|
bool rc;
|
|
|
|
|
|
|
|
switch (fan_status_access_mode) {
|
|
|
|
case TPACPI_FAN_RD_TPEC:
|
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
|
|
|
if (unlikely(!fan_select_fan2()))
|
|
|
|
return -EIO;
|
|
|
|
rc = !acpi_ec_read(fan_rpm_offset, &lo) ||
|
|
|
|
!acpi_ec_read(fan_rpm_offset + 1, &hi);
|
|
|
|
fan_select_fan1(); /* play it safe */
|
|
|
|
if (rc)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
if (likely(speed))
|
|
|
|
*speed = (hi << 8) | lo;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_set_level(int level)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
|
|
|
case TPACPI_FAN_WR_TPEC:
|
2008-11-09 20:54:02 +08:00
|
|
|
if (!(level & TP_EC_FAN_AUTO) &&
|
|
|
|
!(level & TP_EC_FAN_FULLSPEED) &&
|
2008-01-08 23:02:45 +08:00
|
|
|
((level < 0) || (level > 7)))
|
|
|
|
return -EINVAL;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* 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 */
|
2008-02-05 07:24:56 +08:00
|
|
|
else if (level & TP_EC_FAN_AUTO)
|
2008-01-08 23:02:45 +08:00
|
|
|
level |= 4; /* safety min speed 4 */
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!acpi_ec_write(fan_status_offset, level))
|
|
|
|
return -EIO;
|
|
|
|
else
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
break;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
2009-04-04 12:25:52 +08:00
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_FAN,
|
|
|
|
"fan control: set fan control register to 0x%02x\n", level);
|
2008-01-08 23:02:45 +08:00
|
|
|
return 0;
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_set_level_safe(int level)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
int rc;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (level == TPACPI_FAN_LAST_LEVEL)
|
|
|
|
level = fan_control_desired_level;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
rc = fan_set_level(level);
|
|
|
|
if (!rc)
|
|
|
|
fan_update_desired_level(level);
|
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_set_enable(void)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
u8 s;
|
|
|
|
int rc;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* Don't go out of emergency fan mode */
|
|
|
|
if (s != 7) {
|
|
|
|
s &= 0x07;
|
|
|
|
s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!acpi_ec_write(fan_status_offset, s))
|
|
|
|
rc = -EIO;
|
|
|
|
else {
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
rc = 0;
|
|
|
|
}
|
|
|
|
break;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
|
|
|
rc = fan_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
break;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
s &= 0x07;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* Set fan to at least level 4 */
|
|
|
|
s |= 4;
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
|
2008-01-08 23:02:49 +08:00
|
|
|
rc = -EIO;
|
2008-01-08 23:02:45 +08:00
|
|
|
else
|
|
|
|
rc = 0;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
default:
|
|
|
|
rc = -ENXIO;
|
|
|
|
}
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
mutex_unlock(&fan_mutex);
|
2009-04-04 12:25:52 +08:00
|
|
|
|
|
|
|
if (!rc)
|
|
|
|
vdbg_printk(TPACPI_DBG_FAN,
|
|
|
|
"fan control: set fan control register to 0x%02x\n",
|
|
|
|
s);
|
2008-01-08 23:02:45 +08:00
|
|
|
return rc;
|
2006-11-24 21:47:09 +08:00
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_set_disable(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
int rc;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ERESTARTSYS;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
2006-11-24 21:47:10 +08:00
|
|
|
break;
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
|
|
|
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
|
|
|
|
rc = -EIO;
|
|
|
|
else
|
|
|
|
fan_control_desired_level = 0;
|
2006-11-24 21:47:10 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2008-01-08 23:02:45 +08:00
|
|
|
rc = -ENXIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
if (!rc)
|
|
|
|
vdbg_printk(TPACPI_DBG_FAN,
|
|
|
|
"fan control: set fan control register to 0\n");
|
2007-04-24 22:48:17 +08:00
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int fan_set_speed(int speed)
|
2006-11-24 21:47:10 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
int rc;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2008-01-08 23:02:45 +08:00
|
|
|
return -ERESTARTSYS;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
2006-11-24 21:47:10 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2008-01-08 23:02:45 +08:00
|
|
|
rc = -ENXIO;
|
2006-11-24 21:47:10 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
2006-11-24 21:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fan_watchdog_reset(void)
|
|
|
|
{
|
2007-07-19 10:45:27 +08:00
|
|
|
static int fan_watchdog_active;
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2007-04-28 09:00:10 +08:00
|
|
|
if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
|
|
|
|
return;
|
|
|
|
|
2006-11-24 21:47:14 +08:00
|
|
|
if (fan_watchdog_active)
|
|
|
|
cancel_delayed_work(&fan_watchdog_task);
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
if (fan_watchdog_maxinterval > 0 &&
|
|
|
|
tpacpi_lifecycle != TPACPI_LIFE_EXITING) {
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_watchdog_active = 1;
|
2008-04-26 12:02:28 +08:00
|
|
|
if (!queue_delayed_work(tpacpi_wq, &fan_watchdog_task,
|
2006-11-24 21:47:14 +08:00
|
|
|
msecs_to_jiffies(fan_watchdog_maxinterval
|
|
|
|
* 1000))) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("failed to queue the fan watchdog, "
|
2006-11-24 21:47:14 +08:00
|
|
|
"watchdog will not trigger\n");
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
fan_watchdog_active = 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static void fan_watchdog_fire(struct work_struct *ignored)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
int rc;
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
|
|
|
|
return;
|
2006-11-24 21:47:13 +08:00
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("fan watchdog: enabling fan\n");
|
2008-01-08 23:02:45 +08:00
|
|
|
rc = fan_set_enable();
|
|
|
|
if (rc < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("fan watchdog: error %d while enabling fan, "
|
|
|
|
"will try again later...\n", -rc);
|
2008-01-08 23:02:45 +08:00
|
|
|
/* reschedule for later */
|
|
|
|
fan_watchdog_reset();
|
|
|
|
}
|
|
|
|
}
|
2007-04-24 22:48:18 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
res = fan_get_status_safe(&status);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2006-11-24 21:47:13 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static ssize_t fan_pwm1_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
int res, level;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 2, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
tpacpi_disclose_usertask("hwmon pwm1_enable",
|
|
|
|
"set fan mode to %lu\n", t);
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
2006-11-24 21:47:11 +08:00
|
|
|
default:
|
2008-01-08 23:02:45 +08:00
|
|
|
return -EINVAL;
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
|
|
|
|
res = fan_set_level_safe(level);
|
|
|
|
if (res == -ENXIO)
|
|
|
|
return -EINVAL;
|
|
|
|
else if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
fan_watchdog_reset();
|
|
|
|
|
|
|
|
return count;
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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)
|
2007-04-24 22:48:17 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
int res;
|
|
|
|
u8 status;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
res = fan_get_status_safe(&status);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2007-04-28 09:00:09 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if ((status &
|
|
|
|
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
|
|
|
|
status = fan_control_desired_level;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (status > 7)
|
|
|
|
status = 7;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static ssize_t fan_pwm1_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2006-11-24 21:47:11 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
unsigned long s;
|
2006-11-24 21:47:12 +08:00
|
|
|
int rc;
|
2008-01-08 23:02:45 +08:00
|
|
|
u8 status, newlevel;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (parse_strtoul(buf, 255, &s))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
tpacpi_disclose_usertask("hwmon pwm1",
|
|
|
|
"set fan speed to %lu\n", s);
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* scale down from 0-255 to 0-7 */
|
|
|
|
newlevel = (s >> 5) & 0x07;
|
2007-04-28 09:00:09 +08:00
|
|
|
|
2009-01-11 13:01:04 +08:00
|
|
|
if (mutex_lock_killable(&fan_mutex))
|
2007-10-31 03:46:24 +08:00
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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();
|
2007-04-24 22:48:18 +08:00
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return (rc)? rc : count;
|
|
|
|
}
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static struct device_attribute dev_attr_fan_pwm1 =
|
|
|
|
__ATTR(pwm1, S_IWUSR | S_IRUGO,
|
|
|
|
fan_pwm1_show, fan_pwm1_store);
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* 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;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
res = fan_get_speed(&speed);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", speed);
|
|
|
|
}
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static struct device_attribute dev_attr_fan_fan1_input =
|
|
|
|
__ATTR(fan1_input, S_IRUGO,
|
|
|
|
fan_fan1_input_show, NULL);
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
/* sysfs fan fan2_input ------------------------------------------------ */
|
|
|
|
static ssize_t fan_fan2_input_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
unsigned int speed;
|
|
|
|
|
|
|
|
res = fan2_get_speed(&speed);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_fan_fan2_input =
|
|
|
|
__ATTR(fan2_input, S_IRUGO,
|
|
|
|
fan_fan2_input_show, NULL);
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* 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);
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
2006-11-24 21:47:11 +08:00
|
|
|
{
|
2008-01-08 23:02:45 +08:00
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 120, &t))
|
|
|
|
return -EINVAL;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
fan_watchdog_maxinterval = t;
|
|
|
|
fan_watchdog_reset();
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
tpacpi_disclose_usertask("fan_watchdog", "set to %lu\n", t);
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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,
|
2009-06-18 11:40:17 +08:00
|
|
|
NULL, /* for fan2_input */
|
2008-01-08 23:02:45 +08:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group fan_attr_group = {
|
|
|
|
.attrs = fan_attributes,
|
|
|
|
};
|
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */
|
|
|
|
#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */
|
2009-05-31 00:25:06 +08:00
|
|
|
|
|
|
|
#define TPACPI_FAN_QI(__id1, __id2, __quirks) \
|
|
|
|
{ .vendor = PCI_VENDOR_ID_IBM, \
|
|
|
|
.bios = TPACPI_MATCH_ANY, \
|
|
|
|
.ec = TPID(__id1, __id2), \
|
|
|
|
.quirks = __quirks }
|
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
#define TPACPI_FAN_QL(__id1, __id2, __quirks) \
|
|
|
|
{ .vendor = PCI_VENDOR_ID_LENOVO, \
|
|
|
|
.bios = TPACPI_MATCH_ANY, \
|
|
|
|
.ec = TPID(__id1, __id2), \
|
|
|
|
.quirks = __quirks }
|
|
|
|
|
2009-05-31 00:25:06 +08:00
|
|
|
static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
|
|
|
|
TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1),
|
|
|
|
TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1),
|
|
|
|
TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1),
|
|
|
|
TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1),
|
2009-06-18 11:40:17 +08:00
|
|
|
TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN),
|
2009-05-31 00:25:06 +08:00
|
|
|
};
|
|
|
|
|
2009-06-18 11:40:17 +08:00
|
|
|
#undef TPACPI_FAN_QL
|
2009-05-31 00:25:06 +08:00
|
|
|
#undef TPACPI_FAN_QI
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static int __init fan_init(struct ibm_init_struct *iibm)
|
|
|
|
{
|
|
|
|
int rc;
|
2009-05-31 00:25:06 +08:00
|
|
|
unsigned long quirks;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
|
|
|
"initializing fan subdriver\n");
|
2008-01-08 23:02:45 +08:00
|
|
|
|
|
|
|
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;
|
2009-06-18 11:40:17 +08:00
|
|
|
tp_features.second_fan = 0;
|
2008-01-08 23:02:45 +08:00
|
|
|
fan_control_desired_level = 7;
|
|
|
|
|
2010-05-17 06:45:23 +08:00
|
|
|
if (tpacpi_is_ibm()) {
|
|
|
|
TPACPI_ACPIHANDLE_INIT(fans);
|
|
|
|
TPACPI_ACPIHANDLE_INIT(gfan);
|
|
|
|
TPACPI_ACPIHANDLE_INIT(sfan);
|
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2009-05-31 00:25:06 +08:00
|
|
|
quirks = tpacpi_check_quirks(fan_quirk_table,
|
|
|
|
ARRAY_SIZE(fan_quirk_table));
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
2009-05-31 00:25:06 +08:00
|
|
|
if (quirks & TPACPI_FAN_Q1)
|
|
|
|
fan_quirk1_setup();
|
2009-06-18 11:40:17 +08:00
|
|
|
if (quirks & TPACPI_FAN_2FAN) {
|
|
|
|
tp_features.second_fan = 1;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
|
|
|
"secondary fan support enabled\n");
|
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
} else {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("ThinkPad ACPI EC access misbehaving, "
|
2008-01-08 23:02:45 +08:00
|
|
|
"fan status and control unavailable\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2009-04-04 12:25:52 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
|
|
|
"fan is %s, modes %d, %d\n",
|
2008-01-08 23:02:45 +08:00
|
|
|
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);
|
2006-11-24 21:47:12 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* fan control master switch */
|
|
|
|
if (!fan_control_allowed) {
|
|
|
|
fan_control_access_mode = TPACPI_FAN_WR_NONE;
|
|
|
|
fan_control_commands = 0;
|
2009-04-04 12:25:52 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
2008-01-08 23:02:45 +08:00
|
|
|
"fan control features disabled by parameter\n");
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* update fan_control_desired_level */
|
|
|
|
if (fan_status_access_mode != TPACPI_FAN_NONE)
|
|
|
|
fan_get_status_safe(NULL);
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
if (fan_status_access_mode != TPACPI_FAN_NONE ||
|
|
|
|
fan_control_access_mode != TPACPI_FAN_WR_NONE) {
|
2009-06-18 11:40:17 +08:00
|
|
|
if (tp_features.second_fan) {
|
|
|
|
/* attach second fan tachometer */
|
|
|
|
fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
|
|
|
|
&dev_attr_fan_fan2_input.attr;
|
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
|
|
|
&fan_attr_group);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
2008-06-04 10:36:10 +08:00
|
|
|
|
|
|
|
rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
|
|
|
|
&driver_attr_fan_watchdog);
|
|
|
|
if (rc < 0) {
|
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
|
|
|
|
&fan_attr_group);
|
|
|
|
return rc;
|
|
|
|
}
|
2008-01-08 23:02:45 +08:00
|
|
|
return 0;
|
|
|
|
} else
|
|
|
|
return 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
static void fan_exit(void)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2009-04-04 12:25:52 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_FAN,
|
2008-01-08 23:02:49 +08:00
|
|
|
"cancelling any pending fan watchdog tasks\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
/* FIXME: can we really do this unconditionally? */
|
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
|
2008-01-08 23:02:49 +08:00
|
|
|
driver_remove_file(&tpacpi_hwmon_pdriver.driver,
|
|
|
|
&driver_attr_fan_watchdog);
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
cancel_delayed_work(&fan_watchdog_task);
|
2008-04-26 12:02:28 +08:00
|
|
|
flush_workqueue(tpacpi_wq);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2012-06-28 05:18:44 +08:00
|
|
|
static void fan_suspend(void)
|
2008-10-19 01:23:52 +08:00
|
|
|
{
|
2008-11-09 20:54:02 +08:00
|
|
|
int rc;
|
|
|
|
|
2008-10-19 01:23:52 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Store fan status in cache */
|
2008-11-09 20:54:02 +08:00
|
|
|
fan_control_resume_level = 0;
|
|
|
|
rc = fan_get_status_safe(&fan_control_resume_level);
|
|
|
|
if (rc < 0)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("failed to read fan level for later "
|
|
|
|
"restore during resume: %d\n", rc);
|
2008-11-09 20:54:02 +08:00
|
|
|
|
|
|
|
/* if it is undefined, don't attempt to restore it.
|
|
|
|
* KEEP THIS LAST */
|
2008-10-19 01:23:52 +08:00
|
|
|
if (tp_features.fan_ctrl_status_undef)
|
2008-11-09 20:54:02 +08:00
|
|
|
fan_control_resume_level = 0;
|
2008-10-19 01:23:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fan_resume(void)
|
|
|
|
{
|
|
|
|
u8 current_level = 7;
|
|
|
|
bool do_set = false;
|
2008-11-09 20:54:02 +08:00
|
|
|
int rc;
|
2008-10-19 01:23:52 +08:00
|
|
|
|
|
|
|
/* DSDT *always* updates status on resume */
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
|
|
|
|
if (!fan_control_allowed ||
|
2008-11-09 20:54:02 +08:00
|
|
|
!fan_control_resume_level ||
|
2008-10-19 01:23:52 +08:00
|
|
|
(fan_get_status_safe(¤t_level) < 0))
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (fan_control_access_mode) {
|
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2008-11-09 20:54:02 +08:00
|
|
|
/* never decrease fan level */
|
|
|
|
do_set = (fan_control_resume_level > current_level);
|
2008-10-19 01:23:52 +08:00
|
|
|
break;
|
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
|
|
|
case TPACPI_FAN_WR_TPEC:
|
2008-11-09 20:54:02 +08:00
|
|
|
/* never decrease fan level, scale is:
|
|
|
|
* TP_EC_FAN_FULLSPEED > 7 >= TP_EC_FAN_AUTO
|
|
|
|
*
|
|
|
|
* We expect the firmware to set either 7 or AUTO, but we
|
|
|
|
* handle FULLSPEED out of paranoia.
|
|
|
|
*
|
|
|
|
* So, we can safely only restore FULLSPEED or 7, anything
|
|
|
|
* else could slow the fan. Restoring AUTO is useless, at
|
|
|
|
* best that's exactly what the DSDT already set (it is the
|
|
|
|
* slower it uses).
|
|
|
|
*
|
|
|
|
* Always keep in mind that the DSDT *will* have set the
|
|
|
|
* fans to what the vendor supposes is the best level. We
|
|
|
|
* muck with it only to speed the fan up.
|
|
|
|
*/
|
|
|
|
if (fan_control_resume_level != 7 &&
|
|
|
|
!(fan_control_resume_level & TP_EC_FAN_FULLSPEED))
|
|
|
|
return;
|
|
|
|
else
|
|
|
|
do_set = !(current_level & TP_EC_FAN_FULLSPEED) &&
|
|
|
|
(current_level != fan_control_resume_level);
|
2008-10-19 01:23:52 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (do_set) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("restoring fan level to 0x%02x\n",
|
|
|
|
fan_control_resume_level);
|
2008-11-09 20:54:02 +08:00
|
|
|
rc = fan_set_level_safe(fan_control_resume_level);
|
|
|
|
if (rc < 0)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("failed to restore fan level: %d\n", rc);
|
2008-10-19 01:23:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
static int fan_read(struct seq_file *m)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 status;
|
|
|
|
unsigned int speed = 0;
|
|
|
|
|
|
|
|
switch (fan_status_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_ACPI_GFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
/* 570, 600e/x, 770e, 770x */
|
2008-01-08 23:02:49 +08:00
|
|
|
rc = fan_get_status_safe(&status);
|
|
|
|
if (rc < 0)
|
2007-03-24 04:33:57 +08:00
|
|
|
return rc;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\t%s\n"
|
2007-03-24 04:33:57 +08:00
|
|
|
"level:\t\t%d\n",
|
|
|
|
(status != 0) ? "enabled" : "disabled", status);
|
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_TPEC:
|
2007-03-24 04:33:57 +08:00
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
2008-01-08 23:02:49 +08:00
|
|
|
rc = fan_get_status_safe(&status);
|
|
|
|
if (rc < 0)
|
2007-03-24 04:33:57 +08:00
|
|
|
return rc;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\t%s\n",
|
2007-03-24 04:33:57 +08:00
|
|
|
(status != 0) ? "enabled" : "disabled");
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
rc = fan_get_speed(&speed);
|
|
|
|
if (rc < 0)
|
2007-03-24 04:33:57 +08:00
|
|
|
return rc;
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "speed:\t\t%d\n", speed);
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (status & TP_EC_FAN_FULLSPEED)
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Disengaged mode takes precedence */
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\tdisengaged\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
else if (status & TP_EC_FAN_AUTO)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\tauto\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
else
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "level:\t\t%d\n", status);
|
2007-03-24 04:33:57 +08:00
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_NONE:
|
2007-03-24 04:33:57 +08:00
|
|
|
default:
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "status:\t\tnot supported\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tlevel <level>");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, " (<level> is 0-7)\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, " (<level> is 0-7, "
|
2007-04-24 22:48:17 +08:00
|
|
|
"auto, disengaged, full-speed)\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tenable, disable\n"
|
2008-01-08 23:02:49 +08:00
|
|
|
"commands:\twatchdog <timeout> (<timeout> "
|
|
|
|
"is 0 (off), 1-120 (seconds))\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
|
2009-12-16 07:51:12 +08:00
|
|
|
seq_printf(m, "commands:\tspeed <speed>"
|
2007-03-24 04:33:57 +08:00
|
|
|
" (<speed> is 0-65535)\n");
|
|
|
|
|
2009-12-16 07:51:12 +08:00
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_write_cmd_level(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int level;
|
|
|
|
|
2006-11-24 21:47:13 +08:00
|
|
|
if (strlencmp(cmd, "level auto") == 0)
|
2007-04-21 22:08:28 +08:00
|
|
|
level = TP_EC_FAN_AUTO;
|
2007-04-24 22:48:17 +08:00
|
|
|
else if ((strlencmp(cmd, "level disengaged") == 0) |
|
2008-01-08 23:02:49 +08:00
|
|
|
(strlencmp(cmd, "level full-speed") == 0))
|
2007-04-21 22:08:28 +08:00
|
|
|
level = TP_EC_FAN_FULLSPEED;
|
2006-11-24 21:47:13 +08:00
|
|
|
else if (sscanf(cmd, "level %d", &level) != 1)
|
2006-11-24 21:47:11 +08:00
|
|
|
return 0;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
*rc = fan_set_level_safe(level);
|
|
|
|
if (*rc == -ENXIO)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("level command accepted for unsupported access mode %d\n",
|
|
|
|
fan_control_access_mode);
|
2009-04-04 12:25:52 +08:00
|
|
|
else if (!*rc)
|
|
|
|
tpacpi_disclose_usertask("procfs fan",
|
|
|
|
"set level to %d\n", level);
|
2006-11-24 21:47:11 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_enable(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
if (strlencmp(cmd, "enable") != 0)
|
|
|
|
return 0;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
*rc = fan_set_enable();
|
|
|
|
if (*rc == -ENXIO)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("enable command accepted for unsupported access mode %d\n",
|
|
|
|
fan_control_access_mode);
|
2009-04-04 12:25:52 +08:00
|
|
|
else if (!*rc)
|
|
|
|
tpacpi_disclose_usertask("procfs fan", "enable\n");
|
2006-11-24 21:47:11 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_disable(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
if (strlencmp(cmd, "disable") != 0)
|
|
|
|
return 0;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
*rc = fan_set_disable();
|
|
|
|
if (*rc == -ENXIO)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("disable command accepted for unsupported access mode %d\n",
|
|
|
|
fan_control_access_mode);
|
2009-04-04 12:25:52 +08:00
|
|
|
else if (!*rc)
|
|
|
|
tpacpi_disclose_usertask("procfs fan", "disable\n");
|
2006-11-24 21:47:11 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_speed(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int speed;
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
/* TODO:
|
|
|
|
* Support speed <low> <medium> <high> ? */
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
if (sscanf(cmd, "speed %d", &speed) != 1)
|
|
|
|
return 0;
|
|
|
|
|
2008-01-08 23:02:49 +08:00
|
|
|
*rc = fan_set_speed(speed);
|
|
|
|
if (*rc == -ENXIO)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("speed command accepted for unsupported access mode %d\n",
|
|
|
|
fan_control_access_mode);
|
2009-04-04 12:25:52 +08:00
|
|
|
else if (!*rc)
|
|
|
|
tpacpi_disclose_usertask("procfs fan",
|
|
|
|
"set speed to %d\n", speed);
|
2006-11-24 21:47:11 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:14 +08:00
|
|
|
static int fan_write_cmd_watchdog(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int interval;
|
|
|
|
|
|
|
|
if (sscanf(cmd, "watchdog %d", &interval) != 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (interval < 0 || interval > 120)
|
|
|
|
*rc = -EINVAL;
|
2009-04-04 12:25:52 +08:00
|
|
|
else {
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_watchdog_maxinterval = interval;
|
2009-04-04 12:25:52 +08:00
|
|
|
tpacpi_disclose_usertask("procfs fan",
|
|
|
|
"set watchdog timer to %d\n",
|
|
|
|
interval);
|
|
|
|
}
|
2006-11-24 21:47:14 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
while (!rc && (cmd = next_cmd(&buf))) {
|
2007-04-21 22:08:28 +08:00
|
|
|
if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
fan_write_cmd_level(cmd, &rc)) &&
|
2007-04-21 22:08:28 +08:00
|
|
|
!((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
(fan_write_cmd_enable(cmd, &rc) ||
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_write_cmd_disable(cmd, &rc) ||
|
|
|
|
fan_write_cmd_watchdog(cmd, &rc))) &&
|
2007-04-21 22:08:28 +08:00
|
|
|
!((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
fan_write_cmd_speed(cmd, &rc))
|
|
|
|
)
|
|
|
|
rc = -EINVAL;
|
2006-11-24 21:47:14 +08:00
|
|
|
else if (!rc)
|
|
|
|
fan_watchdog_reset();
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct fan_driver_data = {
|
|
|
|
.name = "fan",
|
|
|
|
.read = fan_read,
|
|
|
|
.write = fan_write,
|
|
|
|
.exit = fan_exit,
|
2008-10-19 01:23:52 +08:00
|
|
|
.suspend = fan_suspend,
|
|
|
|
.resume = fan_resume,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Infrastructure
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2009-09-21 01:09:26 +08:00
|
|
|
/*
|
|
|
|
* HKEY event callout for other subdrivers go here
|
|
|
|
* (yes, it is ugly, but it is quick, safe, and gets the job done
|
|
|
|
*/
|
|
|
|
static void tpacpi_driver_event(const unsigned int hkey_event)
|
|
|
|
{
|
2009-12-09 09:36:24 +08:00
|
|
|
if (ibm_backlight_device) {
|
|
|
|
switch (hkey_event) {
|
|
|
|
case TP_HKEY_EV_BRGHT_UP:
|
|
|
|
case TP_HKEY_EV_BRGHT_DOWN:
|
|
|
|
tpacpi_brightness_notify_change();
|
|
|
|
}
|
|
|
|
}
|
2009-12-16 07:51:11 +08:00
|
|
|
if (alsa_card) {
|
|
|
|
switch (hkey_event) {
|
|
|
|
case TP_HKEY_EV_VOL_UP:
|
|
|
|
case TP_HKEY_EV_VOL_DOWN:
|
|
|
|
case TP_HKEY_EV_VOL_MUTE:
|
|
|
|
volume_alsa_notify_change();
|
|
|
|
}
|
|
|
|
}
|
2009-09-21 01:09:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_driver_event(const unsigned int scancode)
|
|
|
|
{
|
2009-09-21 01:09:27 +08:00
|
|
|
tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
|
2009-09-21 01:09:26 +08:00
|
|
|
}
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
/* sysfs name ---------------------------------------------------------- */
|
|
|
|
static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2008-01-08 23:02:48 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME);
|
2007-09-25 17:38:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
|
|
|
|
__ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* /proc support */
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct proc_dir_entry *proc_dir;
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*
|
|
|
|
* Module and infrastructure proble, init and exit handling
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-01-13 07:02:20 +08:00
|
|
|
static bool force_load;
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
|
2007-04-21 22:08:33 +08:00
|
|
|
static const char * __init str_supported(int is_supported)
|
2007-04-21 22:08:32 +08:00
|
|
|
{
|
2007-04-21 22:08:33 +08:00
|
|
|
static char text_unsupported[] __initdata = "not supported";
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
|
2007-04-21 22:08:32 +08:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init ibm_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret;
|
2007-04-21 22:08:33 +08:00
|
|
|
struct ibm_struct *ibm = iibm->data;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct proc_dir_entry *entry;
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
BUG_ON(ibm == NULL);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&ibm->all_drivers);
|
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
if (ibm->flags.experimental && !experimental)
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"probing for %s\n", ibm->name);
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
if (iibm->init) {
|
|
|
|
ret = iibm->init(iibm);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (ret > 0)
|
|
|
|
return 0; /* probe failed */
|
|
|
|
if (ret)
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
2007-04-21 22:08:33 +08:00
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.init_called = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->acpi) {
|
|
|
|
if (ibm->acpi->hid) {
|
|
|
|
ret = register_tpacpi_subdriver(ibm);
|
|
|
|
if (ret)
|
|
|
|
goto err_out;
|
|
|
|
}
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->acpi->notify) {
|
|
|
|
ret = setup_acpi_notify(ibm);
|
|
|
|
if (ret == -ENODEV) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("disabling subdriver %s\n",
|
|
|
|
ibm->name);
|
2007-04-21 22:08:37 +08:00
|
|
|
ret = 0;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_out;
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"%s installed\n", ibm->name);
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ibm->read) {
|
2011-07-24 15:36:29 +08:00
|
|
|
umode_t mode = iibm->base_procfs_mode;
|
2009-12-16 07:51:12 +08:00
|
|
|
|
2010-02-26 09:22:22 +08:00
|
|
|
if (!mode)
|
|
|
|
mode = S_IRUGO;
|
2009-12-16 07:51:12 +08:00
|
|
|
if (ibm->write)
|
|
|
|
mode |= S_IWUSR;
|
|
|
|
entry = proc_create_data(ibm->name, mode, proc_dir,
|
|
|
|
&dispatch_proc_fops, ibm);
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!entry) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to create proc entry %s\n", ibm->name);
|
2007-04-21 22:08:31 +08:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_out;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.proc_created = 1;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2007-04-21 22:08:31 +08:00
|
|
|
|
|
|
|
err_out:
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"%s: at error exit path with result %d\n",
|
|
|
|
ibm->name, ret);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
ibm_exit(ibm);
|
|
|
|
return (ret < 0)? ret : 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Probing */
|
|
|
|
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
static bool __pure __init tpacpi_is_fw_digit(const char c)
|
|
|
|
{
|
|
|
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z');
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */
|
|
|
|
static bool __pure __init tpacpi_is_valid_fw_id(const char* const s,
|
|
|
|
const char t)
|
|
|
|
{
|
|
|
|
return s && strlen(s) >= 8 &&
|
|
|
|
tpacpi_is_fw_digit(s[0]) &&
|
|
|
|
tpacpi_is_fw_digit(s[1]) &&
|
|
|
|
s[2] == t && s[3] == 'T' &&
|
|
|
|
tpacpi_is_fw_digit(s[4]) &&
|
2011-04-01 06:22:33 +08:00
|
|
|
tpacpi_is_fw_digit(s[5]);
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
}
|
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
/* returns 0 - probe ok, or < 0 - probe error.
|
|
|
|
* Probe ok doesn't mean thinkpad found.
|
|
|
|
* On error, kfree() cleanup on tp->* is not performed, caller must do it */
|
|
|
|
static int __must_check __init get_thinkpad_model_data(
|
|
|
|
struct thinkpad_id_data *tp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-10-04 03:15:40 +08:00
|
|
|
const struct dmi_device *dev = NULL;
|
2007-03-24 04:33:57 +08:00
|
|
|
char ec_fw_string[18];
|
2008-07-21 20:15:51 +08:00
|
|
|
char const *s;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
if (!tp)
|
2008-07-21 20:15:51 +08:00
|
|
|
return -EINVAL;
|
2007-07-19 10:45:42 +08:00
|
|
|
|
|
|
|
memset(tp, 0, sizeof(*tp));
|
|
|
|
|
|
|
|
if (dmi_name_in_vendors("IBM"))
|
|
|
|
tp->vendor = PCI_VENDOR_ID_IBM;
|
|
|
|
else if (dmi_name_in_vendors("LENOVO"))
|
|
|
|
tp->vendor = PCI_VENDOR_ID_LENOVO;
|
|
|
|
else
|
2008-07-21 20:15:51 +08:00
|
|
|
return 0;
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
s = dmi_get_system_info(DMI_BIOS_VERSION);
|
|
|
|
tp->bios_version_str = kstrdup(s, GFP_KERNEL);
|
|
|
|
if (s && !tp->bios_version_str)
|
|
|
|
return -ENOMEM;
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
|
|
|
|
/* Really ancient ThinkPad 240X will fail this, which is fine */
|
|
|
|
if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E'))
|
2008-07-21 20:15:51 +08:00
|
|
|
return 0;
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
tp->bios_model = tp->bios_version_str[0]
|
|
|
|
| (tp->bios_version_str[1] << 8);
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
tp->bios_release = (tp->bios_version_str[4] << 8)
|
|
|
|
| tp->bios_version_str[5];
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*
|
|
|
|
* ThinkPad T23 or newer, A31 or newer, R50e or newer,
|
|
|
|
* X32 or newer, all Z series; Some models must have an
|
|
|
|
* up-to-date BIOS or they will not be detected.
|
|
|
|
*
|
|
|
|
* See http://thinkwiki.org/wiki/List_of_DMI_IDs
|
|
|
|
*/
|
|
|
|
while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
|
|
|
|
if (sscanf(dev->name,
|
|
|
|
"IBM ThinkPad Embedded Controller -[%17c",
|
|
|
|
ec_fw_string) == 1) {
|
|
|
|
ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
|
|
|
|
ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
|
2007-07-19 10:45:42 +08:00
|
|
|
|
|
|
|
tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
|
2008-07-21 20:15:51 +08:00
|
|
|
if (!tp->ec_version_str)
|
|
|
|
return -ENOMEM;
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
|
|
|
|
if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) {
|
|
|
|
tp->ec_model = ec_fw_string[0]
|
|
|
|
| (ec_fw_string[1] << 8);
|
|
|
|
tp->ec_release = (ec_fw_string[4] << 8)
|
|
|
|
| ec_fw_string[5];
|
|
|
|
} else {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_notice("ThinkPad firmware release %s "
|
|
|
|
"doesn't match the known patterns\n",
|
|
|
|
ec_fw_string);
|
|
|
|
pr_notice("please report this to %s\n",
|
|
|
|
TPACPI_MAIL);
|
thinkpad-acpi: store fw version with strict checking
Extend the thinkpad model and firmware identification data with the
release serial number for the BIOS and firmware (when available), as
that is easier to parse and compare than the version strings.
We're going to greatly extend the use of the ThinkPad DMI data through
quirk lists, so it is best to be quite strict and make sure what we
get from DMI is exactly what we expect, otherwise quirk matching may
result in quite insane things.
IBM (and Lenovo, at least for the ThinkPad line) uses this schema for
firmware versioning and model:
Firmware model: Two digits, [0-9A-Z]
Firmware version: AABBCCDD, where
AA = firmware model, see above
BB = "ET" for BIOS, "HT" for EC
CC = release version, two digits, [0-9A-Z],
"00" < "09" < "0A" < "10" < "A0" < "ZZ"
DD = "WW"
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-05-31 00:25:05 +08:00
|
|
|
}
|
2007-07-19 10:45:42 +08:00
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
s = dmi_get_system_info(DMI_PRODUCT_VERSION);
|
2012-03-10 07:32:24 +08:00
|
|
|
if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) {
|
2008-07-21 20:15:51 +08:00
|
|
|
tp->model_str = kstrdup(s, GFP_KERNEL);
|
|
|
|
if (!tp->model_str)
|
|
|
|
return -ENOMEM;
|
thinkpad-acpi: recognize latest V-Series using DMI_BIOS_VENDOR
In the latest V-series bios DMI_PRODUCT_VERSION does not contain
the string Lenovo or Thinkpad, but is set to the model number, this
causes the thinkpad_acpi module to fail to load. Recognize laptop
as Lenovo using DMI_BIOS_VENDOR instead, which is set to Lenovo.
Test on V490u
=============
== After the patch ==
[ 1350.295757] thinkpad_acpi: ThinkPad ACPI Extras v0.24
[ 1350.295760] thinkpad_acpi: http://ibm-acpi.sf.net/
[ 1350.295761] thinkpad_acpi: ThinkPad BIOS H7ET21WW (1.00 ), EC unknown
[ 1350.295763] thinkpad_acpi: Lenovo LENOVO, model LV5DXXX
[ 1350.296086] thinkpad_acpi: detected a 8-level brightness capable ThinkPad
[ 1350.296694] thinkpad_acpi: radio switch found; radios are enabled
[ 1350.296703] thinkpad_acpi: possible tablet mode switch found; ThinkPad in laptop mode
[ 1350.306466] thinkpad_acpi: rfkill switch tpacpi_bluetooth_sw: radio is unblocked
[ 1350.307082] Registered led device: tpacpi::thinklight
[ 1350.307215] Registered led device: tpacpi::power
[ 1350.307255] Registered led device: tpacpi::standby
[ 1350.307294] Registered led device: tpacpi::thinkvantage
[ 1350.308160] thinkpad_acpi: Standard ACPI backlight interface available, not loading native one
[ 1350.308333] thinkpad_acpi: Console audio control enabled, mode: monitor (read only)
[ 1350.312287] input: ThinkPad Extra Buttons as /devices/platform/thinkpad_acpi/input/input14
== Before the patch ==
sudo modprobe thinkpad_acpi
FATAL: Error inserting thinkpad_acpi (/lib/modules/3.2.0-27-generic/kernel/drivers/platform/x86/thinkpad_acpi.ko): No such device
Test on B485
=============
This patch was also test in a B485 where the thinkpad_acpi module does not
have any issues loading. But, I tested it to make sure this patch does not
break on already functioning models of Lenovo products.
[13486.746359] thinkpad_acpi: ThinkPad ACPI Extras v0.24
[13486.746364] thinkpad_acpi: http://ibm-acpi.sf.net/
[13486.746368] thinkpad_acpi: ThinkPad BIOS HJET15WW(1.01), EC unknown
[13486.746373] thinkpad_acpi: Lenovo Lenovo LB485, model 814TR01
[13486.747300] thinkpad_acpi: detected a 8-level brightness capable ThinkPad
[13486.752435] thinkpad_acpi: rfkill switch tpacpi_bluetooth_sw: radio is unblocked
[13486.752883] Registered led device: tpacpi::thinklight
[13486.752915] thinkpad_acpi: Standard ACPI backlight interface available, not loading native one
[13486.753216] thinkpad_acpi: Console audio control enabled, mode: monitor (read only)
[13486.757147] input: ThinkPad Extra Buttons as /devices/platform/thinkpad_acpi/input/input15
Signed-off-by: Manoj Iyer <manoj.iyer@canonical.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
2012-08-07 07:15:37 +08:00
|
|
|
} else {
|
|
|
|
s = dmi_get_system_info(DMI_BIOS_VENDOR);
|
|
|
|
if (s && !(strnicmp(s, "Lenovo", 6))) {
|
|
|
|
tp->model_str = kstrdup(s, GFP_KERNEL);
|
|
|
|
if (!tp->model_str)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2007-07-19 10:45:42 +08:00
|
|
|
}
|
2008-04-26 12:02:19 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
s = dmi_get_system_info(DMI_PRODUCT_NAME);
|
|
|
|
tp->nummodel_str = kstrdup(s, GFP_KERNEL);
|
|
|
|
if (s && !tp->nummodel_str)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
static int __init probe_for_thinkpad(void)
|
|
|
|
{
|
|
|
|
int is_thinkpad;
|
|
|
|
|
|
|
|
if (acpi_disabled)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2010-05-17 06:45:23 +08:00
|
|
|
/* It would be dangerous to run the driver in this case */
|
|
|
|
if (!tpacpi_is_ibm() && !tpacpi_is_lenovo())
|
|
|
|
return -ENODEV;
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
/*
|
|
|
|
* Non-ancient models have better DMI tagging, but very old models
|
2009-09-13 02:22:13 +08:00
|
|
|
* don't. tpacpi_is_fw_known() is a cheat to help in that case.
|
2007-04-21 22:08:31 +08:00
|
|
|
*/
|
2009-09-13 02:22:13 +08:00
|
|
|
is_thinkpad = (thinkpad_id.model_str != NULL) ||
|
|
|
|
(thinkpad_id.ec_model != 0) ||
|
|
|
|
tpacpi_is_fw_known();
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2010-05-17 06:45:43 +08:00
|
|
|
/* The EC handler is required */
|
|
|
|
tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (!ec_handle) {
|
|
|
|
if (is_thinkpad)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("Not yet supported ThinkPad detected!\n");
|
2007-04-21 22:08:31 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:34 +08:00
|
|
|
if (!is_thinkpad && !force_load)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-17 06:45:31 +08:00
|
|
|
static void __init thinkpad_acpi_init_banner(void)
|
|
|
|
{
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
|
|
|
|
pr_info("%s\n", TPACPI_URL);
|
2010-05-17 06:45:31 +08:00
|
|
|
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("ThinkPad BIOS %s, EC %s\n",
|
2010-05-17 06:45:31 +08:00
|
|
|
(thinkpad_id.bios_version_str) ?
|
|
|
|
thinkpad_id.bios_version_str : "unknown",
|
|
|
|
(thinkpad_id.ec_version_str) ?
|
|
|
|
thinkpad_id.ec_version_str : "unknown");
|
|
|
|
|
|
|
|
BUG_ON(!thinkpad_id.vendor);
|
|
|
|
|
|
|
|
if (thinkpad_id.model_str)
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_info("%s %s, model %s\n",
|
2010-05-17 06:45:31 +08:00
|
|
|
(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
|
|
|
|
"IBM" : ((thinkpad_id.vendor ==
|
|
|
|
PCI_VENDOR_ID_LENOVO) ?
|
|
|
|
"Lenovo" : "Unknown vendor"),
|
|
|
|
thinkpad_id.model_str,
|
|
|
|
(thinkpad_id.nummodel_str) ?
|
|
|
|
thinkpad_id.nummodel_str : "unknown");
|
|
|
|
}
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Module init, exit, parameters */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_init_struct ibms_init[] __initdata = {
|
|
|
|
{
|
|
|
|
.data = &thinkpad_acpi_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = hotkey_init,
|
|
|
|
.data = &hotkey_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = bluetooth_init,
|
|
|
|
.data = &bluetooth_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = wan_init,
|
|
|
|
.data = &wan_driver_data,
|
|
|
|
},
|
2009-01-11 13:01:03 +08:00
|
|
|
{
|
|
|
|
.init = uwb_init,
|
|
|
|
.data = &uwb_driver_data,
|
|
|
|
},
|
2008-02-16 12:17:54 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_VIDEO
|
2007-04-21 22:08:33 +08:00
|
|
|
{
|
|
|
|
.init = video_init,
|
2010-02-26 09:22:22 +08:00
|
|
|
.base_procfs_mode = S_IRUSR,
|
2007-04-21 22:08:33 +08:00
|
|
|
.data = &video_driver_data,
|
|
|
|
},
|
2008-02-16 12:17:54 +08:00
|
|
|
#endif
|
2007-04-21 22:08:33 +08:00
|
|
|
{
|
|
|
|
.init = light_init,
|
|
|
|
.data = &light_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = cmos_init,
|
|
|
|
.data = &cmos_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = led_init,
|
|
|
|
.data = &led_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = beep_init,
|
|
|
|
.data = &beep_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = thermal_init,
|
|
|
|
.data = &thermal_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = brightness_init,
|
|
|
|
.data = &brightness_driver_data,
|
|
|
|
},
|
|
|
|
{
|
2009-12-16 07:51:08 +08:00
|
|
|
.init = volume_init,
|
2007-04-21 22:08:33 +08:00
|
|
|
.data = &volume_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = fan_init,
|
|
|
|
.data = &fan_driver_data,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2007-02-07 05:13:44 +08:00
|
|
|
static int __init set_ibm_param(const char *val, struct kernel_param *kp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
2007-04-21 22:08:33 +08:00
|
|
|
struct ibm_struct *ibm;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-11-18 19:18:29 +08:00
|
|
|
if (!kp || !kp->name || !val)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
|
|
|
|
ibm = ibms_init[i].data;
|
2007-11-18 19:18:29 +08:00
|
|
|
WARN_ON(ibm == NULL);
|
|
|
|
|
|
|
|
if (!ibm || !ibm->name)
|
|
|
|
continue;
|
2007-04-21 22:08:33 +08:00
|
|
|
|
|
|
|
if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
|
|
|
|
if (strlen(val) > sizeof(ibms_init[i].param) - 2)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENOSPC;
|
2007-04-21 22:08:33 +08:00
|
|
|
strcpy(ibms_init[i].param, val);
|
|
|
|
strcat(ibms_init[i].param, ",");
|
2005-08-17 12:00:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2007-04-21 22:08:33 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(experimental, int, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(experimental,
|
2008-01-08 23:02:49 +08:00
|
|
|
"Enables experimental features when non-zero");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:30 +08:00
|
|
|
module_param_named(debug, dbg_level, uint, 0);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
|
2007-04-21 22:08:30 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(force_load, bool, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(force_load,
|
2008-01-08 23:02:49 +08:00
|
|
|
"Attempts to load the driver even on a "
|
|
|
|
"mis-identified ThinkPad when true");
|
2007-04-21 22:08:34 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param_named(fan_control, fan_control_allowed, bool, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(fan_control,
|
2008-01-08 23:02:49 +08:00
|
|
|
"Enables setting fan parameters features when true");
|
2007-04-28 09:00:09 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param_named(brightness_mode, brightness_mode, uint, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(brightness_mode,
|
2008-01-08 23:02:49 +08:00
|
|
|
"Selects brightness control strategy: "
|
2009-04-04 12:25:53 +08:00
|
|
|
"0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
|
2007-07-19 10:45:43 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(brightness_enable, uint, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(brightness_enable,
|
2008-01-08 23:02:49 +08:00
|
|
|
"Enables backlight control when 1, disables when 0");
|
2007-10-31 04:02:07 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(hotkey_report_mode, uint, 0444);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_PARM_DESC(hotkey_report_mode,
|
2008-01-08 23:02:49 +08:00
|
|
|
"used for backwards compatibility with userspace, "
|
|
|
|
"see documentation");
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2009-12-27 08:52:15 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
|
2009-12-16 07:51:08 +08:00
|
|
|
module_param_named(volume_mode, volume_mode, uint, 0444);
|
|
|
|
MODULE_PARM_DESC(volume_mode,
|
|
|
|
"Selects volume control strategy: "
|
|
|
|
"0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
|
|
|
|
|
2009-12-16 07:51:09 +08:00
|
|
|
module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
|
|
|
|
MODULE_PARM_DESC(volume_capabilities,
|
|
|
|
"Selects the mixer capabilites: "
|
|
|
|
"0=auto, 1=volume and mute, 2=mute only");
|
|
|
|
|
2009-12-16 07:51:10 +08:00
|
|
|
module_param_named(volume_control, volume_control_allowed, bool, 0444);
|
|
|
|
MODULE_PARM_DESC(volume_control,
|
|
|
|
"Enables software override for the console audio "
|
|
|
|
"control when true");
|
|
|
|
|
2009-12-16 07:51:11 +08:00
|
|
|
/* ALSA module API parameters */
|
|
|
|
module_param_named(index, alsa_index, int, 0444);
|
|
|
|
MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
|
|
|
|
module_param_named(id, alsa_id, charp, 0444);
|
|
|
|
MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
|
|
|
|
module_param_named(enable, alsa_enable, bool, 0444);
|
|
|
|
MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
|
2009-12-27 08:52:15 +08:00
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
|
2009-12-16 07:51:11 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
#define TPACPI_PARAM(feature) \
|
2008-01-08 23:02:47 +08:00
|
|
|
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
|
2008-02-16 12:17:50 +08:00
|
|
|
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
|
2008-01-08 23:02:49 +08:00
|
|
|
"at module load, see documentation")
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_PARAM(hotkey);
|
|
|
|
TPACPI_PARAM(bluetooth);
|
|
|
|
TPACPI_PARAM(video);
|
|
|
|
TPACPI_PARAM(light);
|
|
|
|
TPACPI_PARAM(cmos);
|
|
|
|
TPACPI_PARAM(led);
|
|
|
|
TPACPI_PARAM(beep);
|
|
|
|
TPACPI_PARAM(brightness);
|
|
|
|
TPACPI_PARAM(volume);
|
|
|
|
TPACPI_PARAM(fan);
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2009-01-11 13:01:00 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(dbg_wlswemul, uint, 0444);
|
2009-01-11 13:01:00 +08:00
|
|
|
MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
|
|
|
|
module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
|
|
|
|
MODULE_PARM_DESC(wlsw_state,
|
|
|
|
"Initial state of the emulated WLSW switch");
|
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(dbg_bluetoothemul, uint, 0444);
|
2009-01-11 13:01:00 +08:00
|
|
|
MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
|
|
|
|
module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
|
|
|
|
MODULE_PARM_DESC(bluetooth_state,
|
|
|
|
"Initial state of the emulated bluetooth switch");
|
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(dbg_wwanemul, uint, 0444);
|
2009-01-11 13:01:00 +08:00
|
|
|
MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
|
|
|
|
module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
|
|
|
|
MODULE_PARM_DESC(wwan_state,
|
|
|
|
"Initial state of the emulated WWAN switch");
|
2009-01-11 13:01:03 +08:00
|
|
|
|
2009-12-09 09:36:27 +08:00
|
|
|
module_param(dbg_uwbemul, uint, 0444);
|
2009-01-11 13:01:03 +08:00
|
|
|
MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
|
|
|
|
module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
|
|
|
|
MODULE_PARM_DESC(uwb_state,
|
|
|
|
"Initial state of the emulated UWB switch");
|
2009-01-11 13:01:00 +08:00
|
|
|
#endif
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
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);
|
2012-07-25 08:45:09 +08:00
|
|
|
kfree(hotkey_keycode_map);
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2008-01-08 23:02:48 +08:00
|
|
|
remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
|
2008-01-08 23:02:45 +08:00
|
|
|
|
2008-04-26 12:02:28 +08:00
|
|
|
if (tpacpi_wq)
|
|
|
|
destroy_workqueue(tpacpi_wq);
|
|
|
|
|
2008-01-08 23:02:45 +08:00
|
|
|
kfree(thinkpad_id.bios_version_str);
|
|
|
|
kfree(thinkpad_id.ec_version_str);
|
|
|
|
kfree(thinkpad_id.model_str);
|
2012-07-25 08:45:08 +08:00
|
|
|
kfree(thinkpad_id.nummodel_str);
|
2008-01-08 23:02:45 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:44 +08:00
|
|
|
|
2007-04-21 22:08:27 +08:00
|
|
|
static int __init thinkpad_acpi_module_init(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret, i;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
tpacpi_lifecycle = TPACPI_LIFE_INIT;
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* Parameter checking */
|
|
|
|
if (hotkey_report_mode > 2)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/* Driver-level probe */
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2008-07-21 20:15:51 +08:00
|
|
|
ret = get_thinkpad_model_data(&thinkpad_id);
|
|
|
|
if (ret) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to get DMI data: %d\n", ret);
|
2008-07-21 20:15:51 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-04-21 22:08:31 +08:00
|
|
|
ret = probe_for_thinkpad();
|
2007-07-19 10:45:42 +08:00
|
|
|
if (ret) {
|
|
|
|
thinkpad_acpi_module_exit();
|
2007-04-21 22:08:31 +08:00
|
|
|
return ret;
|
2007-07-19 10:45:42 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/* Driver initialization */
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2010-05-17 06:45:31 +08:00
|
|
|
thinkpad_acpi_init_banner();
|
|
|
|
tpacpi_check_outdated_fw();
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
TPACPI_ACPIHANDLE_INIT(ecrd);
|
|
|
|
TPACPI_ACPIHANDLE_INIT(ecwr);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-04-26 12:02:28 +08:00
|
|
|
tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME);
|
|
|
|
if (!tpacpi_wq) {
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:48 +08:00
|
|
|
proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!proc_dir) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to create proc dir " TPACPI_PROC_DIR "\n");
|
2007-04-21 22:08:27 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
ret = platform_driver_register(&tpacpi_pdriver);
|
|
|
|
if (ret) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register main platform driver\n");
|
2007-04-24 22:48:12 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-07-28 04:04:40 +08:00
|
|
|
tp_features.platform_drv_registered = 1;
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
ret = platform_driver_register(&tpacpi_hwmon_pdriver);
|
|
|
|
if (ret) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register hwmon platform driver\n");
|
2007-09-25 17:38:03 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
tp_features.sensors_pdrv_registered = 1;
|
|
|
|
|
2007-04-24 22:48:13 +08:00
|
|
|
ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
|
2007-09-23 22:39:07 +08:00
|
|
|
if (!ret) {
|
|
|
|
tp_features.platform_drv_attrs_registered = 1;
|
2008-01-08 23:02:49 +08:00
|
|
|
ret = tpacpi_create_driver_attributes(
|
|
|
|
&tpacpi_hwmon_pdriver.driver);
|
2007-09-23 22:39:07 +08:00
|
|
|
}
|
2007-04-24 22:48:13 +08:00
|
|
|
if (ret) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to create sysfs driver attributes\n");
|
2007-04-24 22:48:13 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-23 22:39:07 +08:00
|
|
|
tp_features.sensors_pdrv_attrs_registered = 1;
|
2007-04-24 22:48:13 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
|
|
|
|
/* Device initialization */
|
2008-01-08 23:02:48 +08:00
|
|
|
tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1,
|
2007-04-24 22:48:12 +08:00
|
|
|
NULL, 0);
|
|
|
|
if (IS_ERR(tpacpi_pdev)) {
|
|
|
|
ret = PTR_ERR(tpacpi_pdev);
|
|
|
|
tpacpi_pdev = NULL;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register platform device\n");
|
2007-04-24 22:48:12 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-25 17:38:03 +08:00
|
|
|
tpacpi_sensors_pdev = platform_device_register_simple(
|
2008-01-08 23:02:49 +08:00
|
|
|
TPACPI_HWMON_DRVR_NAME,
|
|
|
|
-1, NULL, 0);
|
2007-09-25 17:38:03 +08:00
|
|
|
if (IS_ERR(tpacpi_sensors_pdev)) {
|
|
|
|
ret = PTR_ERR(tpacpi_sensors_pdev);
|
|
|
|
tpacpi_sensors_pdev = NULL;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register hwmon platform device\n");
|
2007-09-25 17:38:03 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = device_create_file(&tpacpi_sensors_pdev->dev,
|
|
|
|
&dev_attr_thinkpad_acpi_pdev_name);
|
|
|
|
if (ret) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to create sysfs hwmon device attributes\n");
|
2007-09-25 17:38:03 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
tp_features.sensors_pdev_attrs_registered = 1;
|
|
|
|
tpacpi_hwmon = hwmon_device_register(&tpacpi_sensors_pdev->dev);
|
2007-04-24 22:48:12 +08:00
|
|
|
if (IS_ERR(tpacpi_hwmon)) {
|
|
|
|
ret = PTR_ERR(tpacpi_hwmon);
|
|
|
|
tpacpi_hwmon = NULL;
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register hwmon device\n");
|
2007-04-24 22:48:12 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-23 22:39:01 +08:00
|
|
|
mutex_init(&tpacpi_inputdev_send_mutex);
|
2007-07-19 10:45:34 +08:00
|
|
|
tpacpi_inputdev = input_allocate_device();
|
|
|
|
if (!tpacpi_inputdev) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to allocate input device\n");
|
2007-07-19 10:45:34 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return -ENOMEM;
|
|
|
|
} else {
|
|
|
|
/* Prepare input device, but don't register */
|
|
|
|
tpacpi_inputdev->name = "ThinkPad Extra Buttons";
|
2008-01-08 23:02:48 +08:00
|
|
|
tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
|
2007-07-19 10:45:34 +08:00
|
|
|
tpacpi_inputdev->id.bustype = BUS_HOST;
|
2010-05-17 06:45:23 +08:00
|
|
|
tpacpi_inputdev->id.vendor = thinkpad_id.vendor;
|
2007-07-19 10:45:34 +08:00
|
|
|
tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
|
|
|
|
tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
|
2009-12-09 09:36:26 +08:00
|
|
|
tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
|
2007-07-19 10:45:34 +08:00
|
|
|
}
|
2010-05-17 06:45:33 +08:00
|
|
|
|
|
|
|
/* Init subdriver dependencies */
|
|
|
|
tpacpi_detect_brightness_capabilities();
|
|
|
|
|
|
|
|
/* Init subdrivers */
|
2007-04-21 22:08:33 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
|
|
|
|
ret = ibm_init(&ibms_init[i]);
|
|
|
|
if (ret >= 0 && *ibms_init[i].param)
|
|
|
|
ret = ibms_init[i].data->write(ibms_init[i].param);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ret < 0) {
|
2007-04-21 22:08:27 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
2010-02-26 08:28:58 +08:00
|
|
|
|
|
|
|
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
|
|
|
|
|
2007-07-19 10:45:34 +08:00
|
|
|
ret = input_register_device(tpacpi_inputdev);
|
|
|
|
if (ret < 0) {
|
2011-04-05 01:06:25 +08:00
|
|
|
pr_err("unable to register input device\n");
|
2007-07-19 10:45:34 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
tp_features.input_device_registered = 1;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-04-26 12:02:22 +08:00
|
|
|
MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
|
|
|
|
|
2009-04-14 10:44:13 +08:00
|
|
|
/*
|
|
|
|
* This will autoload the driver in almost every ThinkPad
|
|
|
|
* in widespread use.
|
|
|
|
*
|
|
|
|
* Only _VERY_ old models, like the 240, 240x and 570 lack
|
|
|
|
* the HKEY event interface.
|
|
|
|
*/
|
|
|
|
MODULE_DEVICE_TABLE(acpi, ibm_htk_device_ids);
|
|
|
|
|
2008-01-08 23:02:47 +08:00
|
|
|
/*
|
|
|
|
* 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) \
|
thinkpad-acpi: fix module autoloading for older models
Looking at the source, there seems to be a missing * to match my DMI
string. I mean for newer IBM and Lenovo's laptops you match either one
of the following:
MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
While for older Thinkpads, you do this (for instance):
IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
with IBM_BIOS_MODULE_ALIAS being MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
Note there's no * terminating the string. As result, udev doesn't load
anything because modprobe cannot find anything matching this (my
machine actually):
udevtest: run: '/sbin/modprobe dmi:bvnIBM:bvr1IET71WW(2.10):bd06/16/2006:svnIBM:pn236621U:pvrNotAv
Signed-off-by: Mathieu Chouquet-Stringer <mchouque@free.fr>
Acked-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2009-03-14 23:35:26 +08:00
|
|
|
MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW*")
|
2008-01-08 23:02:47 +08:00
|
|
|
|
|
|
|
/* Ancient thinkpad BIOSes have to be identified by
|
|
|
|
* BIOS type or model number, and there are far less
|
|
|
|
* BIOS types than model numbers... */
|
2009-04-14 10:44:13 +08:00
|
|
|
IBM_BIOS_MODULE_ALIAS("I[MU]"); /* 570, 570e */
|
2008-01-08 23:02:47 +08:00
|
|
|
|
2009-04-14 10:44:12 +08:00
|
|
|
MODULE_AUTHOR("Borislav Deianov <borislav@users.sf.net>");
|
|
|
|
MODULE_AUTHOR("Henrique de Moraes Holschuh <hmh@hmh.eng.br>");
|
2008-01-08 23:02:48 +08:00
|
|
|
MODULE_DESCRIPTION(TPACPI_DESC);
|
|
|
|
MODULE_VERSION(TPACPI_VERSION);
|
2008-01-08 23:02:47 +08:00
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
2007-04-21 22:08:27 +08:00
|
|
|
module_init(thinkpad_acpi_module_init);
|
|
|
|
module_exit(thinkpad_acpi_module_exit);
|