提交 7458bbd0 编写于 作者: L Len Brown

Merge branch 'thinkpad-2.6.33' into release

ThinkPad ACPI Extras Driver ThinkPad ACPI Extras Driver
Version 0.23 Version 0.24
April 10th, 2009 December 11th, 2009
Borislav Deianov <borislav@users.sf.net> Borislav Deianov <borislav@users.sf.net>
Henrique de Moraes Holschuh <hmh@hmh.eng.br> Henrique de Moraes Holschuh <hmh@hmh.eng.br>
...@@ -460,6 +460,8 @@ event code Key Notes ...@@ -460,6 +460,8 @@ event code Key Notes
For Lenovo ThinkPads with a new For Lenovo ThinkPads with a new
BIOS, it has to be handled either BIOS, it has to be handled either
by the ACPI OSI, or by userspace. by the ACPI OSI, or by userspace.
The driver does the right thing,
never mess with this.
0x1011 0x10 FN+END Brightness down. See brightness 0x1011 0x10 FN+END Brightness down. See brightness
up for details. up for details.
...@@ -582,46 +584,15 @@ with hotkey_report_mode. ...@@ -582,46 +584,15 @@ with hotkey_report_mode.
Brightness hotkey notes: Brightness hotkey notes:
These are the current sane choices for brightness key mapping in Don't mess with the brightness hotkeys in a Thinkpad. If you want
thinkpad-acpi: notifications for OSD, use the sysfs backlight class event support.
For IBM and Lenovo models *without* ACPI backlight control (the ones on The driver will issue KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN events
which thinkpad-acpi will autoload its backlight interface by default, automatically for the cases were userspace has to do something to
and on which ACPI video does not export a backlight interface): implement brightness changes. When you override these events, you will
either fail to handle properly the ThinkPads that require explicit
1. Don't enable or map the brightness hotkeys in thinkpad-acpi, as action to change backlight brightness, or the ThinkPads that require
these older firmware versions unfortunately won't respect the hotkey that no action be taken to work properly.
mask for brightness keys anyway, and always reacts to them. This
usually work fine, unless X.org drivers are doing something to block
the BIOS. In that case, use (3) below. This is the default mode of
operation.
2. Enable the hotkeys, but map them to something else that is NOT
KEY_BRIGHTNESS_UP/DOWN or any other keycode that would cause
userspace to try to change the backlight level, and use that as an
on-screen-display hint.
3. IF AND ONLY IF X.org drivers find a way to block the firmware from
automatically changing the brightness, enable the hotkeys and map
them to KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN, and feed that to
something that calls xbacklight. thinkpad-acpi will not be able to
change brightness in that case either, so you should disable its
backlight interface.
For Lenovo models *with* ACPI backlight control:
1. Load up ACPI video and use that. ACPI video will report ACPI
events for brightness change keys. Do not mess with thinkpad-acpi
defaults in this case. thinkpad-acpi should not have anything to do
with backlight events in a scenario where ACPI video is loaded:
brightness hotkeys must be disabled, and the backlight interface is
to be kept disabled as well. This is the default mode of operation.
2. Do *NOT* load up ACPI video, enable the hotkeys in thinkpad-acpi,
and map them to KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN. Process
these keys on userspace somehow (e.g. by calling xbacklight).
The driver will do this automatically if it detects that ACPI video
has been disabled.
Bluetooth Bluetooth
...@@ -1121,25 +1092,61 @@ WARNING: ...@@ -1121,25 +1092,61 @@ WARNING:
its level up and down at every change. its level up and down at every change.
Volume control -- /proc/acpi/ibm/volume Volume control
--------------------------------------- --------------
procfs: /proc/acpi/ibm/volume
ALSA: "ThinkPad Console Audio Control", default ID: "ThinkPadEC"
NOTE: by default, the volume control interface operates in read-only
mode, as it is supposed to be used for on-screen-display purposes.
The read/write mode can be enabled through the use of the
"volume_control=1" module parameter.
This feature allows volume control on ThinkPad models which don't have NOTE: distros are urged to not enable volume_control by default, this
a hardware volume knob. The available commands are: should be done by the local admin only. The ThinkPad UI is for the
console audio control to be done through the volume keys only, and for
the desktop environment to just provide on-screen-display feedback.
Software volume control should be done only in the main AC97/HDA
mixer.
This feature allows volume control on ThinkPad models with a digital
volume knob (when available, not all models have it), as well as
mute/unmute control. The available commands are:
echo up >/proc/acpi/ibm/volume echo up >/proc/acpi/ibm/volume
echo down >/proc/acpi/ibm/volume echo down >/proc/acpi/ibm/volume
echo mute >/proc/acpi/ibm/volume echo mute >/proc/acpi/ibm/volume
echo unmute >/proc/acpi/ibm/volume
echo 'level <level>' >/proc/acpi/ibm/volume echo 'level <level>' >/proc/acpi/ibm/volume
The <level> number range is 0 to 15 although not all of them may be The <level> number range is 0 to 14 although not all of them may be
distinct. The unmute the volume after the mute command, use either the distinct. The unmute the volume after the mute command, use either the
up or down command (the level command will not unmute the volume). up or down command (the level command will not unmute the volume), or
the unmute command.
The current volume level and mute state is shown in the file. The current volume level and mute state is shown in the file.
The ALSA mixer interface to this feature is still missing, but patches You can use the volume_capabilities parameter to tell the driver
to add it exist. That problem should be addressed in the not so whether your thinkpad has volume control or mute-only control:
distant future. volume_capabilities=1 for mixers with mute and volume control,
volume_capabilities=2 for mixers with only mute control.
If the driver misdetects the capabilities for your ThinkPad model,
please report this to ibm-acpi-devel@lists.sourceforge.net, so that we
can update the driver.
There are two strategies for volume control. To select which one
should be used, use the volume_mode module parameter: volume_mode=1
selects EC mode, and volume_mode=3 selects EC mode with NVRAM backing
(so that volume/mute changes are remembered across shutdown/reboot).
The driver will operate in volume_mode=3 by default. If that does not
work well on your ThinkPad model, please report this to
ibm-acpi-devel@lists.sourceforge.net.
The driver supports the standard ALSA module parameters. If the ALSA
mixer is disabled, the driver will disable all volume functionality.
Fan control and monitoring: fan speed, fan enable/disable Fan control and monitoring: fan speed, fan enable/disable
...@@ -1405,6 +1412,7 @@ to enable more than one output class, just add their values. ...@@ -1405,6 +1412,7 @@ to enable more than one output class, just add their values.
0x0008 HKEY event interface, hotkeys 0x0008 HKEY event interface, hotkeys
0x0010 Fan control 0x0010 Fan control
0x0020 Backlight brightness 0x0020 Backlight brightness
0x0040 Audio mixer/volume control
There is also a kernel build option to enable more debugging There is also a kernel build option to enable more debugging
information, which may be necessary to debug driver problems. information, which may be necessary to debug driver problems.
...@@ -1465,3 +1473,9 @@ Sysfs interface changelog: ...@@ -1465,3 +1473,9 @@ Sysfs interface changelog:
and it is always able to disable hot keys. Very old and it is always able to disable hot keys. Very old
thinkpads are properly supported. hotkey_bios_mask thinkpads are properly supported. hotkey_bios_mask
is deprecated and marked for removal. is deprecated and marked for removal.
0x020600: Marker for backlight change event support.
0x020700: Support for mute-only mixers.
Volume control in read-only mode by default.
Marker for ALSA mixer support.
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#define TPACPI_VERSION "0.23" #define TPACPI_VERSION "0.24"
#define TPACPI_SYSFS_VERSION 0x020500 #define TPACPI_SYSFS_VERSION 0x020700
/* /*
* Changelog: * Changelog:
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include <linux/nvram.h> #include <linux/nvram.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/backlight.h> #include <linux/backlight.h>
#include <linux/fb.h> #include <linux/fb.h>
...@@ -76,6 +77,10 @@ ...@@ -76,6 +77,10 @@
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <acpi/acpi_drivers.h> #include <acpi/acpi_drivers.h>
#include <linux/pci_ids.h> #include <linux/pci_ids.h>
...@@ -231,6 +236,7 @@ enum tpacpi_hkey_event_t { ...@@ -231,6 +236,7 @@ enum tpacpi_hkey_event_t {
#define TPACPI_DBG_HKEY 0x0008 #define TPACPI_DBG_HKEY 0x0008
#define TPACPI_DBG_FAN 0x0010 #define TPACPI_DBG_FAN 0x0010
#define TPACPI_DBG_BRGHT 0x0020 #define TPACPI_DBG_BRGHT 0x0020
#define TPACPI_DBG_MIXER 0x0040
#define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off") #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
#define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
...@@ -256,7 +262,7 @@ struct tp_acpi_drv_struct { ...@@ -256,7 +262,7 @@ struct tp_acpi_drv_struct {
struct ibm_struct { struct ibm_struct {
char *name; char *name;
int (*read) (char *); int (*read) (struct seq_file *);
int (*write) (char *); int (*write) (char *);
void (*exit) (void); void (*exit) (void);
void (*resume) (void); void (*resume) (void);
...@@ -298,6 +304,7 @@ static struct { ...@@ -298,6 +304,7 @@ static struct {
u32 fan_ctrl_status_undef:1; u32 fan_ctrl_status_undef:1;
u32 second_fan:1; u32 second_fan:1;
u32 beep_needs_two_args:1; u32 beep_needs_two_args:1;
u32 mixer_no_level_control:1;
u32 input_device_registered:1; u32 input_device_registered:1;
u32 platform_drv_registered:1; u32 platform_drv_registered:1;
u32 platform_drv_attrs_registered:1; u32 platform_drv_attrs_registered:1;
...@@ -309,6 +316,7 @@ static struct { ...@@ -309,6 +316,7 @@ static struct {
static struct { static struct {
u16 hotkey_mask_ff:1; u16 hotkey_mask_ff:1;
u16 volume_ctrl_forbidden:1;
} tp_warned; } tp_warned;
struct thinkpad_id_data { struct thinkpad_id_data {
...@@ -425,6 +433,12 @@ static void tpacpi_log_usertask(const char * const what) ...@@ -425,6 +433,12 @@ static void tpacpi_log_usertask(const char * const what)
.ec = TPACPI_MATCH_ANY, \ .ec = TPACPI_MATCH_ANY, \
.quirks = (__quirk) } .quirks = (__quirk) }
#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
{ .vendor = PCI_VENDOR_ID_LENOVO, \
.bios = TPACPI_MATCH_ANY, \
.ec = TPID(__id1, __id2), \
.quirks = (__quirk) }
struct tpacpi_quirk { struct tpacpi_quirk {
unsigned int vendor; unsigned int vendor;
u16 bios; u16 bios;
...@@ -776,36 +790,25 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) ...@@ -776,36 +790,25 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
**************************************************************************** ****************************************************************************
****************************************************************************/ ****************************************************************************/
static int dispatch_procfs_read(char *page, char **start, off_t off, static int dispatch_proc_show(struct seq_file *m, void *v)
int count, int *eof, void *data)
{ {
struct ibm_struct *ibm = data; struct ibm_struct *ibm = m->private;
int len;
if (!ibm || !ibm->read) if (!ibm || !ibm->read)
return -EINVAL; return -EINVAL;
return ibm->read(m);
}
len = ibm->read(page); static int dispatch_proc_open(struct inode *inode, struct file *file)
if (len < 0) {
return len; return single_open(file, dispatch_proc_show, PDE(inode)->data);
if (len <= off + count)
*eof = 1;
*start = page + off;
len -= off;
if (len > count)
len = count;
if (len < 0)
len = 0;
return len;
} }
static int dispatch_procfs_write(struct file *file, static ssize_t dispatch_proc_write(struct file *file,
const char __user *userbuf, const char __user *userbuf,
unsigned long count, void *data) size_t count, loff_t *pos)
{ {
struct ibm_struct *ibm = data; struct ibm_struct *ibm = PDE(file->f_path.dentry->d_inode)->data;
char *kernbuf; char *kernbuf;
int ret; int ret;
...@@ -834,6 +837,15 @@ static int dispatch_procfs_write(struct file *file, ...@@ -834,6 +837,15 @@ static int dispatch_procfs_write(struct file *file,
return ret; return ret;
} }
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,
};
static char *next_cmd(char **cmds) static char *next_cmd(char **cmds)
{ {
char *start = *cmds; char *start = *cmds;
...@@ -1261,6 +1273,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, ...@@ -1261,6 +1273,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
struct tpacpi_rfk *atp_rfk; struct tpacpi_rfk *atp_rfk;
int res; int res;
bool sw_state = false; bool sw_state = false;
bool hw_state;
int sw_status; int sw_status;
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
...@@ -1295,7 +1308,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, ...@@ -1295,7 +1308,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
rfkill_init_sw_state(atp_rfk->rfkill, sw_state); rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
} }
} }
rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); hw_state = tpacpi_rfk_check_hwblock_state();
rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
res = rfkill_register(atp_rfk->rfkill); res = rfkill_register(atp_rfk->rfkill);
if (res < 0) { if (res < 0) {
...@@ -1308,6 +1322,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, ...@@ -1308,6 +1322,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
} }
tpacpi_rfkill_switches[id] = atp_rfk; tpacpi_rfkill_switches[id] = atp_rfk;
printk(TPACPI_INFO "rfkill switch %s: radio is %sblocked\n",
name, (sw_state || hw_state) ? "" : "un");
return 0; return 0;
} }
...@@ -1380,12 +1397,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id, ...@@ -1380,12 +1397,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
{ {
int len = 0;
if (id >= TPACPI_RFK_SW_MAX) if (id >= TPACPI_RFK_SW_MAX)
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
else { else {
int status; int status;
...@@ -1399,13 +1414,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) ...@@ -1399,13 +1414,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
return status; return status;
} }
len += sprintf(p + len, "status:\t\t%s\n", seq_printf(m, "status:\t\t%s\n",
(status == TPACPI_RFK_RADIO_ON) ? (status == TPACPI_RFK_RADIO_ON) ?
"enabled" : "disabled"); "enabled" : "disabled");
len += sprintf(p + len, "commands:\tenable, disable\n"); seq_printf(m, "commands:\tenable, disable\n");
} }
return len; return 0;
} }
static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf) static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
...@@ -1776,7 +1791,7 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { ...@@ -1776,7 +1791,7 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */ TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */
TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */ TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */
TPV_QL0('7', 'E', 'D', '0'), /* R60e, R60i */ TPV_QL1('7', 'E', 'D', '0', '1', '5'), /* R60e, R60i */
/* BIOS FW BIOS VERS EC FW EC VERS */ /* BIOS FW BIOS VERS EC FW EC VERS */
TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */ TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */
...@@ -1792,8 +1807,8 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { ...@@ -1792,8 +1807,8 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */ TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */
TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */ TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */
TPV_QL0('7', 'B', 'D', '7'), /* X60/s */ TPV_QL1('7', 'B', 'D', '7', '4', '0'), /* X60/s */
TPV_QL0('7', 'J', '3', '0'), /* X60t */ TPV_QL1('7', 'J', '3', '0', '1', '3'), /* X60t */
/* (0) - older versions lack DMI EC fw string and functionality */ /* (0) - older versions lack DMI EC fw string and functionality */
/* (1) - older versions known to lack functionality */ /* (1) - older versions known to lack functionality */
...@@ -1883,14 +1898,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm) ...@@ -1883,14 +1898,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
return 0; return 0;
} }
static int thinkpad_acpi_driver_read(char *p) static int thinkpad_acpi_driver_read(struct seq_file *m)
{ {
int len = 0; seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC); return 0;
len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION);
return len;
} }
static struct ibm_struct thinkpad_acpi_driver_data = { static struct ibm_struct thinkpad_acpi_driver_data = {
...@@ -2186,6 +2198,7 @@ static int hotkey_mask_set(u32 mask) ...@@ -2186,6 +2198,7 @@ static int hotkey_mask_set(u32 mask)
fwmask, hotkey_acpi_mask); fwmask, hotkey_acpi_mask);
} }
if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
hotkey_mask_warn_incomplete_mask(); hotkey_mask_warn_incomplete_mask();
return rc; return rc;
...@@ -3182,6 +3195,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3182,6 +3195,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
int res, i; int res, i;
int status; int status;
int hkeyv; int hkeyv;
bool radiosw_state = false;
bool tabletsw_state = false;
unsigned long quirks; unsigned long quirks;
...@@ -3287,6 +3302,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3287,6 +3302,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if (dbg_wlswemul) { if (dbg_wlswemul) {
tp_features.hotkey_wlsw = 1; tp_features.hotkey_wlsw = 1;
radiosw_state = !!tpacpi_wlsw_emulstate;
printk(TPACPI_INFO printk(TPACPI_INFO
"radio switch emulation enabled\n"); "radio switch emulation enabled\n");
} else } else
...@@ -3294,6 +3310,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3294,6 +3310,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
/* Not all thinkpads have a hardware radio switch */ /* Not all thinkpads have a hardware radio switch */
if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) { if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
tp_features.hotkey_wlsw = 1; tp_features.hotkey_wlsw = 1;
radiosw_state = !!status;
printk(TPACPI_INFO printk(TPACPI_INFO
"radio switch found; radios are %s\n", "radio switch found; radios are %s\n",
enabled(status, 0)); enabled(status, 0));
...@@ -3305,11 +3322,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3305,11 +3322,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
/* For X41t, X60t, X61t Tablets... */ /* For X41t, X60t, X61t Tablets... */
if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) { if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
tp_features.hotkey_tablet = 1; tp_features.hotkey_tablet = 1;
tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
printk(TPACPI_INFO printk(TPACPI_INFO
"possible tablet mode switch found; " "possible tablet mode switch found; "
"ThinkPad in %s mode\n", "ThinkPad in %s mode\n",
(status & TP_HOTKEY_TABLET_MASK)? (tabletsw_state) ? "tablet" : "laptop");
"tablet" : "laptop");
res = add_to_attr_set(hotkey_dev_attributes, res = add_to_attr_set(hotkey_dev_attributes,
&dev_attr_hotkey_tablet_mode.attr); &dev_attr_hotkey_tablet_mode.attr);
} }
...@@ -3344,16 +3361,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3344,16 +3361,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
TPACPI_HOTKEY_MAP_SIZE); TPACPI_HOTKEY_MAP_SIZE);
} }
set_bit(EV_KEY, tpacpi_inputdev->evbit); input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
set_bit(EV_MSC, tpacpi_inputdev->evbit);
set_bit(MSC_SCAN, tpacpi_inputdev->mscbit);
tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN; tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
tpacpi_inputdev->keycode = hotkey_keycode_map; tpacpi_inputdev->keycode = hotkey_keycode_map;
for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) { for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
if (hotkey_keycode_map[i] != KEY_RESERVED) { if (hotkey_keycode_map[i] != KEY_RESERVED) {
set_bit(hotkey_keycode_map[i], input_set_capability(tpacpi_inputdev, EV_KEY,
tpacpi_inputdev->keybit); hotkey_keycode_map[i]);
} else { } else {
if (i < sizeof(hotkey_reserved_mask)*8) if (i < sizeof(hotkey_reserved_mask)*8)
hotkey_reserved_mask |= 1 << i; hotkey_reserved_mask |= 1 << i;
...@@ -3361,12 +3376,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3361,12 +3376,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
} }
if (tp_features.hotkey_wlsw) { if (tp_features.hotkey_wlsw) {
set_bit(EV_SW, tpacpi_inputdev->evbit); input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit); input_report_switch(tpacpi_inputdev,
SW_RFKILL_ALL, radiosw_state);
} }
if (tp_features.hotkey_tablet) { if (tp_features.hotkey_tablet) {
set_bit(EV_SW, tpacpi_inputdev->evbit); input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit); input_report_switch(tpacpi_inputdev,
SW_TABLET_MODE, tabletsw_state);
} }
/* Do not issue duplicate brightness change events to /* Do not issue duplicate brightness change events to
...@@ -3433,8 +3450,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) ...@@ -3433,8 +3450,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
tpacpi_inputdev->close = &hotkey_inputdev_close; tpacpi_inputdev->close = &hotkey_inputdev_close;
hotkey_poll_setup_safe(true); hotkey_poll_setup_safe(true);
tpacpi_send_radiosw_update();
tpacpi_input_send_tabletsw();
return 0; return 0;
...@@ -3542,49 +3557,57 @@ static bool hotkey_notify_usrevent(const u32 hkey, ...@@ -3542,49 +3557,57 @@ static bool hotkey_notify_usrevent(const u32 hkey,
} }
} }
static void thermal_dump_all_sensors(void);
static bool hotkey_notify_thermal(const u32 hkey, static bool hotkey_notify_thermal(const u32 hkey,
bool *send_acpi_ev, bool *send_acpi_ev,
bool *ignore_acpi_ev) bool *ignore_acpi_ev)
{ {
bool known = true;
/* 0x6000-0x6FFF: thermal alarms */ /* 0x6000-0x6FFF: thermal alarms */
*send_acpi_ev = true; *send_acpi_ev = true;
*ignore_acpi_ev = false; *ignore_acpi_ev = false;
switch (hkey) { switch (hkey) {
case TP_HKEY_EV_THM_TABLE_CHANGED:
printk(TPACPI_INFO
"EC reports that Thermal Table has changed\n");
/* recommended action: do nothing, we don't have
* Lenovo ATM information */
return true;
case TP_HKEY_EV_ALARM_BAT_HOT: case TP_HKEY_EV_ALARM_BAT_HOT:
printk(TPACPI_CRIT printk(TPACPI_CRIT
"THERMAL ALARM: battery is too hot!\n"); "THERMAL ALARM: battery is too hot!\n");
/* recommended action: warn user through gui */ /* recommended action: warn user through gui */
return true; break;
case TP_HKEY_EV_ALARM_BAT_XHOT: case TP_HKEY_EV_ALARM_BAT_XHOT:
printk(TPACPI_ALERT printk(TPACPI_ALERT
"THERMAL EMERGENCY: battery is extremely hot!\n"); "THERMAL EMERGENCY: battery is extremely hot!\n");
/* recommended action: immediate sleep/hibernate */ /* recommended action: immediate sleep/hibernate */
return true; break;
case TP_HKEY_EV_ALARM_SENSOR_HOT: case TP_HKEY_EV_ALARM_SENSOR_HOT:
printk(TPACPI_CRIT printk(TPACPI_CRIT
"THERMAL ALARM: " "THERMAL ALARM: "
"a sensor reports something is too hot!\n"); "a sensor reports something is too hot!\n");
/* recommended action: warn user through gui, that */ /* recommended action: warn user through gui, that */
/* some internal component is too hot */ /* some internal component is too hot */
return true; break;
case TP_HKEY_EV_ALARM_SENSOR_XHOT: case TP_HKEY_EV_ALARM_SENSOR_XHOT:
printk(TPACPI_ALERT printk(TPACPI_ALERT
"THERMAL EMERGENCY: " "THERMAL EMERGENCY: "
"a sensor reports something is extremely hot!\n"); "a sensor reports something is extremely hot!\n");
/* recommended action: immediate sleep/hibernate */ /* recommended action: immediate sleep/hibernate */
return true; break;
case TP_HKEY_EV_THM_TABLE_CHANGED:
printk(TPACPI_INFO
"EC reports that Thermal Table has changed\n");
/* recommended action: do nothing, we don't have
* Lenovo ATM information */
return true;
default: default:
printk(TPACPI_ALERT printk(TPACPI_ALERT
"THERMAL ALERT: unknown thermal alarm received\n"); "THERMAL ALERT: unknown thermal alarm received\n");
return false; known = false;
} }
thermal_dump_all_sensors();
return known;
} }
static void hotkey_notify(struct ibm_struct *ibm, u32 event) static void hotkey_notify(struct ibm_struct *ibm, u32 event)
...@@ -3727,14 +3750,13 @@ static void hotkey_resume(void) ...@@ -3727,14 +3750,13 @@ static void hotkey_resume(void)
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
static int hotkey_read(char *p) static int hotkey_read(struct seq_file *m)
{ {
int res, status; int res, status;
int len = 0;
if (!tp_features.hotkey) { if (!tp_features.hotkey) {
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
return len; return 0;
} }
if (mutex_lock_killable(&hotkey_mutex)) if (mutex_lock_killable(&hotkey_mutex))
...@@ -3746,17 +3768,16 @@ static int hotkey_read(char *p) ...@@ -3746,17 +3768,16 @@ static int hotkey_read(char *p)
if (res) if (res)
return res; return res;
len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
if (hotkey_all_mask) { if (hotkey_all_mask) {
len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_user_mask); seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
len += sprintf(p + len, seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
"commands:\tenable, disable, reset, <mask>\n");
} else { } else {
len += sprintf(p + len, "mask:\t\tnot supported\n"); seq_printf(m, "mask:\t\tnot supported\n");
len += sprintf(p + len, "commands:\tenable, disable, reset\n"); seq_printf(m, "commands:\tenable, disable, reset\n");
} }
return len; return 0;
} }
static void hotkey_enabledisable_warn(bool enable) static void hotkey_enabledisable_warn(bool enable)
...@@ -3863,15 +3884,6 @@ enum { ...@@ -3863,15 +3884,6 @@ enum {
#define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw" #define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw"
static void bluetooth_suspend(pm_message_t state)
{
/* Try to make sure radio will resume powered off */
if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
TP_ACPI_BLTH_PWR_OFF_ON_RESUME))
vdbg_printk(TPACPI_DBG_RFKILL,
"bluetooth power down on resume request failed\n");
}
static int bluetooth_get_status(void) static int bluetooth_get_status(void)
{ {
int status; int status;
...@@ -3905,10 +3917,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state) ...@@ -3905,10 +3917,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state)
#endif #endif
/* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */
status = TP_ACPI_BLUETOOTH_RESUMECTRL;
if (state == TPACPI_RFK_RADIO_ON) if (state == TPACPI_RFK_RADIO_ON)
status = TP_ACPI_BLUETOOTH_RADIOSSW; status |= TP_ACPI_BLUETOOTH_RADIOSSW;
else
status = 0;
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
return -EIO; return -EIO;
...@@ -4032,9 +4043,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) ...@@ -4032,9 +4043,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
static int bluetooth_read(char *p) static int bluetooth_read(struct seq_file *m)
{ {
return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p); return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
} }
static int bluetooth_write(char *buf) static int bluetooth_write(char *buf)
...@@ -4047,7 +4058,6 @@ static struct ibm_struct bluetooth_driver_data = { ...@@ -4047,7 +4058,6 @@ static struct ibm_struct bluetooth_driver_data = {
.read = bluetooth_read, .read = bluetooth_read,
.write = bluetooth_write, .write = bluetooth_write,
.exit = bluetooth_exit, .exit = bluetooth_exit,
.suspend = bluetooth_suspend,
.shutdown = bluetooth_shutdown, .shutdown = bluetooth_shutdown,
}; };
...@@ -4065,15 +4075,6 @@ enum { ...@@ -4065,15 +4075,6 @@ enum {
#define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw"
static void wan_suspend(pm_message_t state)
{
/* Try to make sure radio will resume powered off */
if (!acpi_evalf(NULL, NULL, "\\WGSV", "qvd",
TP_ACPI_WGSV_PWR_OFF_ON_RESUME))
vdbg_printk(TPACPI_DBG_RFKILL,
"WWAN power down on resume request failed\n");
}
static int wan_get_status(void) static int wan_get_status(void)
{ {
int status; int status;
...@@ -4106,11 +4107,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state) ...@@ -4106,11 +4107,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state)
} }
#endif #endif
/* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */ /* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */
status = TP_ACPI_WANCARD_RESUMECTRL;
if (state == TPACPI_RFK_RADIO_ON) if (state == TPACPI_RFK_RADIO_ON)
status = TP_ACPI_WANCARD_RADIOSSW; status |= TP_ACPI_WANCARD_RADIOSSW;
else
status = 0;
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
return -EIO; return -EIO;
...@@ -4233,9 +4233,9 @@ static int __init wan_init(struct ibm_init_struct *iibm) ...@@ -4233,9 +4233,9 @@ static int __init wan_init(struct ibm_init_struct *iibm)
} }
/* procfs -------------------------------------------------------------- */ /* procfs -------------------------------------------------------------- */
static int wan_read(char *p) static int wan_read(struct seq_file *m)
{ {
return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p); return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
} }
static int wan_write(char *buf) static int wan_write(char *buf)
...@@ -4248,7 +4248,6 @@ static struct ibm_struct wan_driver_data = { ...@@ -4248,7 +4248,6 @@ static struct ibm_struct wan_driver_data = {
.read = wan_read, .read = wan_read,
.write = wan_write, .write = wan_write,
.exit = wan_exit, .exit = wan_exit,
.suspend = wan_suspend,
.shutdown = wan_shutdown, .shutdown = wan_shutdown,
}; };
...@@ -4611,14 +4610,13 @@ static int video_expand_toggle(void) ...@@ -4611,14 +4610,13 @@ static int video_expand_toggle(void)
/* not reached */ /* not reached */
} }
static int video_read(char *p) static int video_read(struct seq_file *m)
{ {
int status, autosw; int status, autosw;
int len = 0;
if (video_supported == TPACPI_VIDEO_NONE) { if (video_supported == TPACPI_VIDEO_NONE) {
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
return len; return 0;
} }
status = video_outputsw_get(); status = video_outputsw_get();
...@@ -4629,20 +4627,20 @@ static int video_read(char *p) ...@@ -4629,20 +4627,20 @@ static int video_read(char *p)
if (autosw < 0) if (autosw < 0)
return autosw; return autosw;
len += sprintf(p + len, "status:\t\tsupported\n"); seq_printf(m, "status:\t\tsupported\n");
len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
if (video_supported == TPACPI_VIDEO_NEW) if (video_supported == TPACPI_VIDEO_NEW)
len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
if (video_supported == TPACPI_VIDEO_NEW) if (video_supported == TPACPI_VIDEO_NEW)
len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); seq_printf(m, "commands:\tauto_enable, auto_disable\n");
len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
return len; return 0;
} }
static int video_write(char *buf) static int video_write(char *buf)
...@@ -4834,25 +4832,24 @@ static void light_exit(void) ...@@ -4834,25 +4832,24 @@ static void light_exit(void)
flush_workqueue(tpacpi_wq); flush_workqueue(tpacpi_wq);
} }
static int light_read(char *p) static int light_read(struct seq_file *m)
{ {
int len = 0;
int status; int status;
if (!tp_features.light) { if (!tp_features.light) {
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
} else if (!tp_features.light_status) { } else if (!tp_features.light_status) {
len += sprintf(p + len, "status:\t\tunknown\n"); seq_printf(m, "status:\t\tunknown\n");
len += sprintf(p + len, "commands:\ton, off\n"); seq_printf(m, "commands:\ton, off\n");
} else { } else {
status = light_get_status(); status = light_get_status();
if (status < 0) if (status < 0)
return status; return status;
len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
len += sprintf(p + len, "commands:\ton, off\n"); seq_printf(m, "commands:\ton, off\n");
} }
return len; return 0;
} }
static int light_write(char *buf) static int light_write(char *buf)
...@@ -4930,20 +4927,18 @@ static void cmos_exit(void) ...@@ -4930,20 +4927,18 @@ static void cmos_exit(void)
device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
} }
static int cmos_read(char *p) static int cmos_read(struct seq_file *m)
{ {
int len = 0;
/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
R30, R31, T20-22, X20-21 */ R30, R31, T20-22, X20-21 */
if (!cmos_handle) if (!cmos_handle)
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
else { else {
len += sprintf(p + len, "status:\t\tsupported\n"); seq_printf(m, "status:\t\tsupported\n");
len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n"); seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
} }
return len; return 0;
} }
static int cmos_write(char *buf) static int cmos_write(char *buf)
...@@ -5318,15 +5313,13 @@ static int __init led_init(struct ibm_init_struct *iibm) ...@@ -5318,15 +5313,13 @@ static int __init led_init(struct ibm_init_struct *iibm)
((s) == TPACPI_LED_OFF ? "off" : \ ((s) == TPACPI_LED_OFF ? "off" : \
((s) == TPACPI_LED_ON ? "on" : "blinking")) ((s) == TPACPI_LED_ON ? "on" : "blinking"))
static int led_read(char *p) static int led_read(struct seq_file *m)
{ {
int len = 0;
if (!led_supported) { if (!led_supported) {
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
return len; return 0;
} }
len += sprintf(p + len, "status:\t\tsupported\n"); seq_printf(m, "status:\t\tsupported\n");
if (led_supported == TPACPI_LED_570) { if (led_supported == TPACPI_LED_570) {
/* 570 */ /* 570 */
...@@ -5335,15 +5328,15 @@ static int led_read(char *p) ...@@ -5335,15 +5328,15 @@ static int led_read(char *p)
status = led_get_status(i); status = led_get_status(i);
if (status < 0) if (status < 0)
return -EIO; return -EIO;
len += sprintf(p + len, "%d:\t\t%s\n", seq_printf(m, "%d:\t\t%s\n",
i, str_led_status(status)); i, str_led_status(status));
} }
} }
len += sprintf(p + len, "commands:\t" seq_printf(m, "commands:\t"
"<led> on, <led> off, <led> blink (<led> is 0-15)\n"); "<led> on, <led> off, <led> blink (<led> is 0-15)\n");
return len; return 0;
} }
static int led_write(char *buf) static int led_write(char *buf)
...@@ -5416,18 +5409,16 @@ static int __init beep_init(struct ibm_init_struct *iibm) ...@@ -5416,18 +5409,16 @@ static int __init beep_init(struct ibm_init_struct *iibm)
return (beep_handle)? 0 : 1; return (beep_handle)? 0 : 1;
} }
static int beep_read(char *p) static int beep_read(struct seq_file *m)
{ {
int len = 0;
if (!beep_handle) if (!beep_handle)
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
else { else {
len += sprintf(p + len, "status:\t\tsupported\n"); seq_printf(m, "status:\t\tsupported\n");
len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n"); seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
} }
return len; return 0;
} }
static int beep_write(char *buf) static int beep_write(char *buf)
...@@ -5480,8 +5471,11 @@ enum { /* TPACPI_THERMAL_TPEC_* */ ...@@ -5480,8 +5471,11 @@ enum { /* TPACPI_THERMAL_TPEC_* */
TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ 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_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */
TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */
TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */
}; };
#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ #define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
struct ibm_thermal_sensors_struct { struct ibm_thermal_sensors_struct {
s32 temp[TPACPI_MAX_THERMAL_SENSORS]; s32 temp[TPACPI_MAX_THERMAL_SENSORS];
...@@ -5571,6 +5565,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) ...@@ -5571,6 +5565,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
return n; return n;
} }
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;
printk(TPACPI_NOTICE
"temperatures (Celsius):");
for (i = 0; i < n; i++) {
if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA)
printk(KERN_CONT " %d", (int)(t.temp[i] / 1000));
else
printk(KERN_CONT " N/A");
}
printk(KERN_CONT "\n");
}
/* sysfs temp##_input -------------------------------------------------- */ /* sysfs temp##_input -------------------------------------------------- */
static ssize_t thermal_temp_input_show(struct device *dev, static ssize_t thermal_temp_input_show(struct device *dev,
...@@ -5586,7 +5602,7 @@ static ssize_t thermal_temp_input_show(struct device *dev, ...@@ -5586,7 +5602,7 @@ static ssize_t thermal_temp_input_show(struct device *dev,
res = thermal_get_sensor(idx, &value); res = thermal_get_sensor(idx, &value);
if (res) if (res)
return res; return res;
if (value == TP_EC_THERMAL_TMP_NA * 1000) if (value == TPACPI_THERMAL_SENSOR_NA)
return -ENXIO; return -ENXIO;
return snprintf(buf, PAGE_SIZE, "%d\n", value); return snprintf(buf, PAGE_SIZE, "%d\n", value);
...@@ -5763,9 +5779,8 @@ static void thermal_exit(void) ...@@ -5763,9 +5779,8 @@ static void thermal_exit(void)
} }
} }
static int thermal_read(char *p) static int thermal_read(struct seq_file *m)
{ {
int len = 0;
int n, i; int n, i;
struct ibm_thermal_sensors_struct t; struct ibm_thermal_sensors_struct t;
...@@ -5773,16 +5788,16 @@ static int thermal_read(char *p) ...@@ -5773,16 +5788,16 @@ static int thermal_read(char *p)
if (unlikely(n < 0)) if (unlikely(n < 0))
return n; return n;
len += sprintf(p + len, "temperatures:\t"); seq_printf(m, "temperatures:\t");
if (n > 0) { if (n > 0) {
for (i = 0; i < (n - 1); i++) for (i = 0; i < (n - 1); i++)
len += sprintf(p + len, "%d ", t.temp[i] / 1000); seq_printf(m, "%d ", t.temp[i] / 1000);
len += sprintf(p + len, "%d\n", t.temp[i] / 1000); seq_printf(m, "%d\n", t.temp[i] / 1000);
} else } else
len += sprintf(p + len, "not supported\n"); seq_printf(m, "not supported\n");
return len; return 0;
} }
static struct ibm_struct thermal_driver_data = { static struct ibm_struct thermal_driver_data = {
...@@ -5797,39 +5812,38 @@ static struct ibm_struct thermal_driver_data = { ...@@ -5797,39 +5812,38 @@ static struct ibm_struct thermal_driver_data = {
static u8 ecdump_regs[256]; static u8 ecdump_regs[256];
static int ecdump_read(char *p) static int ecdump_read(struct seq_file *m)
{ {
int len = 0;
int i, j; int i, j;
u8 v; u8 v;
len += sprintf(p + len, "EC " seq_printf(m, "EC "
" +00 +01 +02 +03 +04 +05 +06 +07" " +00 +01 +02 +03 +04 +05 +06 +07"
" +08 +09 +0a +0b +0c +0d +0e +0f\n"); " +08 +09 +0a +0b +0c +0d +0e +0f\n");
for (i = 0; i < 256; i += 16) { for (i = 0; i < 256; i += 16) {
len += sprintf(p + len, "EC 0x%02x:", i); seq_printf(m, "EC 0x%02x:", i);
for (j = 0; j < 16; j++) { for (j = 0; j < 16; j++) {
if (!acpi_ec_read(i + j, &v)) if (!acpi_ec_read(i + j, &v))
break; break;
if (v != ecdump_regs[i + j]) if (v != ecdump_regs[i + j])
len += sprintf(p + len, " *%02x", v); seq_printf(m, " *%02x", v);
else else
len += sprintf(p + len, " %02x", v); seq_printf(m, " %02x", v);
ecdump_regs[i + j] = v; ecdump_regs[i + j] = v;
} }
len += sprintf(p + len, "\n"); seq_putc(m, '\n');
if (j != 16) if (j != 16)
break; break;
} }
/* These are way too dangerous to advertise openly... */ /* These are way too dangerous to advertise openly... */
#if 0 #if 0
len += sprintf(p + len, "commands:\t0x<offset> 0x<value>" seq_printf(m, "commands:\t0x<offset> 0x<value>"
" (<offset> is 00-ff, <value> is 00-ff)\n"); " (<offset> is 00-ff, <value> is 00-ff)\n");
len += sprintf(p + len, "commands:\t0x<offset> <value> " seq_printf(m, "commands:\t0x<offset> <value> "
" (<offset> is 00-ff, <value> is 0-255)\n"); " (<offset> is 00-ff, <value> is 0-255)\n");
#endif #endif
return len; return 0;
} }
static int ecdump_write(char *buf) static int ecdump_write(char *buf)
...@@ -6092,6 +6106,12 @@ static int brightness_get(struct backlight_device *bd) ...@@ -6092,6 +6106,12 @@ static int brightness_get(struct backlight_device *bd)
return status & TP_EC_BACKLIGHT_LVLMSK; return status & TP_EC_BACKLIGHT_LVLMSK;
} }
static void tpacpi_brightness_notify_change(void)
{
backlight_force_update(ibm_backlight_device,
BACKLIGHT_UPDATE_HOTKEY);
}
static struct backlight_ops ibm_backlight_data = { static struct backlight_ops ibm_backlight_data = {
.get_brightness = brightness_get, .get_brightness = brightness_get,
.update_status = brightness_update_status, .update_status = brightness_update_status,
...@@ -6120,8 +6140,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { ...@@ -6120,8 +6140,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
/* Models with Intel Extreme Graphics 2 */ /* Models with Intel Extreme Graphics 2 */
TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC),
TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC), 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_NOEC), TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
/* Models with Intel GMA900 */ /* Models with Intel GMA900 */
TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */ TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */
...@@ -6246,6 +6266,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm) ...@@ -6246,6 +6266,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK; ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
backlight_update_status(ibm_backlight_device); backlight_update_status(ibm_backlight_device);
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
| TP_ACPI_HKEY_BRGHTDWN_MASK);;
return 0; return 0;
} }
...@@ -6270,23 +6296,22 @@ static void brightness_exit(void) ...@@ -6270,23 +6296,22 @@ static void brightness_exit(void)
tpacpi_brightness_checkpoint_nvram(); tpacpi_brightness_checkpoint_nvram();
} }
static int brightness_read(char *p) static int brightness_read(struct seq_file *m)
{ {
int len = 0;
int level; int level;
level = brightness_get(NULL); level = brightness_get(NULL);
if (level < 0) { if (level < 0) {
len += sprintf(p + len, "level:\t\tunreadable\n"); seq_printf(m, "level:\t\tunreadable\n");
} else { } else {
len += sprintf(p + len, "level:\t\t%d\n", level); seq_printf(m, "level:\t\t%d\n", level);
len += sprintf(p + len, "commands:\tup, down\n"); seq_printf(m, "commands:\tup, down\n");
len += sprintf(p + len, "commands:\tlevel <level>" seq_printf(m, "commands:\tlevel <level>"
" (<level> is 0-%d)\n", " (<level> is 0-%d)\n",
(tp_features.bright_16levels) ? 15 : 7); (tp_features.bright_16levels) ? 15 : 7);
} }
return len; return 0;
} }
static int brightness_write(char *buf) static int brightness_write(char *buf)
...@@ -6322,6 +6347,9 @@ static int brightness_write(char *buf) ...@@ -6322,6 +6347,9 @@ static int brightness_write(char *buf)
* Doing it this way makes the syscall restartable in case of EINTR * Doing it this way makes the syscall restartable in case of EINTR
*/ */
rc = brightness_set(level); rc = brightness_set(level);
if (!rc && ibm_backlight_device)
backlight_force_update(ibm_backlight_device,
BACKLIGHT_UPDATE_SYSFS);
return (rc == -EINTR)? -ERESTARTSYS : rc; return (rc == -EINTR)? -ERESTARTSYS : rc;
} }
...@@ -6338,99 +6366,654 @@ static struct ibm_struct brightness_driver_data = { ...@@ -6338,99 +6366,654 @@ static struct ibm_struct brightness_driver_data = {
* Volume subdriver * Volume subdriver
*/ */
static int volume_offset = 0x30; /*
* 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.
*/
#define TPACPI_ALSA_DRVNAME "ThinkPad EC"
#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
static int alsa_index = SNDRV_DEFAULT_IDX1;
static char *alsa_id = "ThinkPadEC";
static int alsa_enable = SNDRV_DEFAULT_ENABLE1;
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;
enum {
TP_EC_AUDIO = 0x30,
static int volume_read(char *p) /* 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
};
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
};
static enum tpacpi_volume_access_mode volume_mode =
TPACPI_VOL_MODE_MAX;
static enum tpacpi_volume_capabilities volume_capabilities;
static int volume_control_allowed;
/*
* 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)
{ {
int len = 0; u8 lec = 0;
u8 level; u8 b_nvram;
u8 ec_mask;
if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
return;
if (!volume_control_allowed)
return;
vdbg_printk(TPACPI_DBG_MIXER,
"trying to checkpoint mixer state to NVRAM...\n");
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;
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 (!acpi_ec_read(volume_offset, &level)) { if (lec != (b_nvram & ec_mask)) {
len += sprintf(p + len, "level:\t\tunreadable\n"); /* 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);
} else { } else {
len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); vdbg_printk(TPACPI_DBG_MIXER,
len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); "NVRAM mixer status already is 0x%02x (0x%02x)\n",
len += sprintf(p + len, "commands:\tup, down, mute\n"); (unsigned int) lec, (unsigned int) b_nvram);
len += sprintf(p + len, "commands:\tlevel <level>"
" (<level> is 0-15)\n");
} }
return len; unlock:
mutex_unlock(&volume_mutex);
} }
static int volume_write(char *buf) static int volume_get_status_ec(u8 *status)
{ {
int cmos_cmd, inc, i; u8 s;
u8 level, mute;
int new_level, new_mute;
char *cmd;
while ((cmd = next_cmd(&buf))) { if (!acpi_ec_read(TP_EC_AUDIO, &s))
if (!acpi_ec_read(volume_offset, &level))
return -EIO; return -EIO;
new_mute = mute = level & 0x40;
new_level = level = level & 0xf;
if (strlencmp(cmd, "up") == 0) { *status = s;
if (mute)
new_mute = 0;
else
new_level = level == 15 ? 15 : level + 1;
} else if (strlencmp(cmd, "down") == 0) {
if (mute)
new_mute = 0;
else
new_level = level == 0 ? 0 : level - 1;
} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
new_level >= 0 && new_level <= 15) {
/* new_level set */
} else if (strlencmp(cmd, "mute") == 0) {
new_mute = 0x40;
} else
return -EINVAL;
if (new_level != level) { dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
/* mute doesn't change */
cmos_cmd = (new_level > level) ? return 0;
TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; }
inc = new_level > level ? 1 : -1;
if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || static int volume_get_status(u8 *status)
!acpi_ec_write(volume_offset, level))) {
return -EIO; return volume_get_status_ec(status);
}
for (i = level; i != new_level; i += inc) static int volume_set_status_ec(const u8 status)
if (issue_thinkpad_cmos_command(cmos_cmd) || {
!acpi_ec_write(volume_offset, i + inc)) if (!acpi_ec_write(TP_EC_AUDIO, status))
return -EIO; return -EIO;
if (mute && dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
(issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
!acpi_ec_write(volume_offset, new_level + mute))) { return 0;
return -EIO; }
static int volume_set_status(const u8 status)
{
return volume_set_status_ec(status);
}
static int volume_set_mute_ec(const bool mute)
{
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;
if (n != s)
rc = volume_set_status_ec(n);
unlock:
mutex_unlock(&volume_mutex);
return rc;
}
static int volume_set_mute(const bool mute)
{
dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
(mute) ? "" : "un");
return volume_set_mute_ec(mute);
}
static int volume_set_volume_ec(const u8 vol)
{
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;
if (n != s)
rc = volume_set_status_ec(n);
unlock:
mutex_unlock(&volume_mutex);
return rc;
}
static int volume_set_volume(const u8 vol)
{
dbg_printk(TPACPI_DBG_MIXER,
"trying to set volume level to %hu\n", vol);
return volume_set_volume_ec(vol);
}
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)
{
return volume_set_volume(ucontrol->value.integer.value[0]);
}
#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)
{
return volume_set_mute(!ucontrol->value.integer.value[0]);
}
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,
};
static void volume_suspend(pm_message_t state)
{
tpacpi_volume_checkpoint_nvram();
}
static void volume_resume(void)
{
volume_alsa_notify_change();
}
static void volume_shutdown(void)
{
tpacpi_volume_checkpoint_nvram();
}
static void volume_exit(void)
{
if (alsa_card) {
snd_card_free(alsa_card);
alsa_card = NULL;
} }
tpacpi_volume_checkpoint_nvram();
}
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);
if (rc < 0)
return rc;
if (!card)
return -ENOMEM;
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 (new_mute != mute) { if (!tp_features.mixer_no_level_control) {
/* level doesn't change */ ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
rc = snd_ctl_add(card, ctl_vol);
if (rc < 0) {
printk(TPACPI_ERR
"Failed to create ALSA volume control\n");
goto err_out;
}
data->ctl_vol_id = &ctl_vol->id;
}
ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
rc = snd_ctl_add(card, ctl_mute);
if (rc < 0) {
printk(TPACPI_ERR "Failed to create ALSA mute control\n");
goto err_out;
}
data->ctl_mute_id = &ctl_mute->id;
cmos_cmd = (new_mute) ? snd_card_set_dev(card, &tpacpi_pdev->dev);
TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; rc = snd_card_register(card);
if (issue_thinkpad_cmos_command(cmos_cmd) || err_out:
!acpi_ec_write(volume_offset, level + new_mute)) if (rc < 0) {
return -EIO; snd_card_free(card);
card = NULL;
}
alsa_card = card;
return rc;
}
#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 }
};
static int __init volume_init(struct ibm_init_struct *iibm)
{
unsigned long quirks;
int rc;
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) {
printk(TPACPI_ERR
"UCMS step volume mode not implemented, "
"please contact %s\n", TPACPI_MAIL);
return 1;
}
if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
return -EINVAL;
/*
* 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;
}
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);
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,
"mute is supported, volume control is %s\n",
str_supported(!tp_features.mixer_no_level_control));
rc = volume_create_alsa_mixer();
if (rc) {
printk(TPACPI_ERR
"Could not create the ALSA mixer interface\n");
return rc;
}
printk(TPACPI_INFO
"Console audio control enabled, mode: %s\n",
(volume_control_allowed) ?
"override (read/write)" :
"monitor (read only)");
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);
return 0;
}
static int volume_read(struct seq_file *m)
{
u8 status;
if (volume_get_status(&status) < 0) {
seq_printf(m, "level:\t\tunreadable\n");
} else {
if (tp_features.mixer_no_level_control)
seq_printf(m, "level:\t\tunsupported\n");
else
seq_printf(m, "level:\t\t%d\n",
status & TP_EC_AUDIO_LVL_MSK);
seq_printf(m, "mute:\t\t%s\n",
onoff(status, TP_EC_AUDIO_MUTESW));
if (volume_control_allowed) {
seq_printf(m, "commands:\tunmute, mute\n");
if (!tp_features.mixer_no_level_control) {
seq_printf(m,
"commands:\tup, down\n");
seq_printf(m,
"commands:\tlevel <level>"
" (<level> is 0-%d)\n",
TP_EC_VOLUME_MAX);
}
} }
} }
return 0; return 0;
} }
static int volume_write(char *buf)
{
u8 s;
u8 new_level, new_mute;
int l;
char *cmd;
int rc;
/*
* 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;
printk(TPACPI_NOTICE
"Console audio control in monitor mode, "
"changes are not allowed.\n");
printk(TPACPI_NOTICE
"Use the volume_control=1 module parameter "
"to enable volume control\n");
}
return -EPERM;
}
rc = volume_get_status(&s);
if (rc < 0)
return rc;
new_level = s & TP_EC_AUDIO_LVL_MSK;
new_mute = s & TP_EC_AUDIO_MUTESW_MSK;
while ((cmd = next_cmd(&buf))) {
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)
new_mute = TP_EC_AUDIO_MUTESW_MSK;
else if (strlencmp(cmd, "unmute") == 0)
new_mute = 0;
else
return -EINVAL;
}
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);
}
volume_alsa_notify_change();
return (rc == -EINTR) ? -ERESTARTSYS : rc;
}
static struct ibm_struct volume_driver_data = { static struct ibm_struct volume_driver_data = {
.name = "volume", .name = "volume",
.read = volume_read, .read = volume_read,
.write = volume_write, .write = volume_write,
.exit = volume_exit,
.suspend = volume_suspend,
.resume = volume_resume,
.shutdown = volume_shutdown,
}; };
/************************************************************************* /*************************************************************************
...@@ -7507,9 +8090,8 @@ static void fan_resume(void) ...@@ -7507,9 +8090,8 @@ static void fan_resume(void)
} }
} }
static int fan_read(char *p) static int fan_read(struct seq_file *m)
{ {
int len = 0;
int rc; int rc;
u8 status; u8 status;
unsigned int speed = 0; unsigned int speed = 0;
...@@ -7521,7 +8103,7 @@ static int fan_read(char *p) ...@@ -7521,7 +8103,7 @@ static int fan_read(char *p)
if (rc < 0) if (rc < 0)
return rc; return rc;
len += sprintf(p + len, "status:\t\t%s\n" seq_printf(m, "status:\t\t%s\n"
"level:\t\t%d\n", "level:\t\t%d\n",
(status != 0) ? "enabled" : "disabled", status); (status != 0) ? "enabled" : "disabled", status);
break; break;
...@@ -7532,54 +8114,54 @@ static int fan_read(char *p) ...@@ -7532,54 +8114,54 @@ static int fan_read(char *p)
if (rc < 0) if (rc < 0)
return rc; return rc;
len += sprintf(p + len, "status:\t\t%s\n", seq_printf(m, "status:\t\t%s\n",
(status != 0) ? "enabled" : "disabled"); (status != 0) ? "enabled" : "disabled");
rc = fan_get_speed(&speed); rc = fan_get_speed(&speed);
if (rc < 0) if (rc < 0)
return rc; return rc;
len += sprintf(p + len, "speed:\t\t%d\n", speed); seq_printf(m, "speed:\t\t%d\n", speed);
if (status & TP_EC_FAN_FULLSPEED) if (status & TP_EC_FAN_FULLSPEED)
/* Disengaged mode takes precedence */ /* Disengaged mode takes precedence */
len += sprintf(p + len, "level:\t\tdisengaged\n"); seq_printf(m, "level:\t\tdisengaged\n");
else if (status & TP_EC_FAN_AUTO) else if (status & TP_EC_FAN_AUTO)
len += sprintf(p + len, "level:\t\tauto\n"); seq_printf(m, "level:\t\tauto\n");
else else
len += sprintf(p + len, "level:\t\t%d\n", status); seq_printf(m, "level:\t\t%d\n", status);
break; break;
case TPACPI_FAN_NONE: case TPACPI_FAN_NONE:
default: default:
len += sprintf(p + len, "status:\t\tnot supported\n"); seq_printf(m, "status:\t\tnot supported\n");
} }
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) { if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
len += sprintf(p + len, "commands:\tlevel <level>"); seq_printf(m, "commands:\tlevel <level>");
switch (fan_control_access_mode) { switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_SFAN: case TPACPI_FAN_WR_ACPI_SFAN:
len += sprintf(p + len, " (<level> is 0-7)\n"); seq_printf(m, " (<level> is 0-7)\n");
break; break;
default: default:
len += sprintf(p + len, " (<level> is 0-7, " seq_printf(m, " (<level> is 0-7, "
"auto, disengaged, full-speed)\n"); "auto, disengaged, full-speed)\n");
break; break;
} }
} }
if (fan_control_commands & TPACPI_FAN_CMD_ENABLE) if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
len += sprintf(p + len, "commands:\tenable, disable\n" seq_printf(m, "commands:\tenable, disable\n"
"commands:\twatchdog <timeout> (<timeout> " "commands:\twatchdog <timeout> (<timeout> "
"is 0 (off), 1-120 (seconds))\n"); "is 0 (off), 1-120 (seconds))\n");
if (fan_control_commands & TPACPI_FAN_CMD_SPEED) if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
len += sprintf(p + len, "commands:\tspeed <speed>" seq_printf(m, "commands:\tspeed <speed>"
" (<speed> is 0-65535)\n"); " (<speed> is 0-65535)\n");
return len; return 0;
} }
static int fan_write_cmd_level(const char *cmd, int *rc) static int fan_write_cmd_level(const char *cmd, int *rc)
...@@ -7721,10 +8303,23 @@ static struct ibm_struct fan_driver_data = { ...@@ -7721,10 +8303,23 @@ static struct ibm_struct fan_driver_data = {
*/ */
static void tpacpi_driver_event(const unsigned int hkey_event) static void tpacpi_driver_event(const unsigned int hkey_event)
{ {
if (ibm_backlight_device) {
switch (hkey_event) {
case TP_HKEY_EV_BRGHT_UP:
case TP_HKEY_EV_BRGHT_DOWN:
tpacpi_brightness_notify_change();
}
}
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();
}
}
} }
static void hotkey_driver_event(const unsigned int scancode) static void hotkey_driver_event(const unsigned int scancode)
{ {
tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode); tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
...@@ -7853,19 +8448,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm) ...@@ -7853,19 +8448,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
"%s installed\n", ibm->name); "%s installed\n", ibm->name);
if (ibm->read) { if (ibm->read) {
entry = create_proc_entry(ibm->name, mode_t mode;
S_IFREG | S_IRUGO | S_IWUSR,
proc_dir); mode = S_IRUGO;
if (ibm->write)
mode |= S_IWUSR;
entry = proc_create_data(ibm->name, mode, proc_dir,
&dispatch_proc_fops, ibm);
if (!entry) { if (!entry) {
printk(TPACPI_ERR "unable to create proc entry %s\n", printk(TPACPI_ERR "unable to create proc entry %s\n",
ibm->name); ibm->name);
ret = -ENODEV; ret = -ENODEV;
goto err_out; goto err_out;
} }
entry->data = ibm;
entry->read_proc = &dispatch_procfs_read;
if (ibm->write)
entry->write_proc = &dispatch_procfs_write;
ibm->flags.proc_created = 1; ibm->flags.proc_created = 1;
} }
...@@ -8077,6 +8672,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { ...@@ -8077,6 +8672,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.data = &brightness_driver_data, .data = &brightness_driver_data,
}, },
{ {
.init = volume_init,
.data = &volume_driver_data, .data = &volume_driver_data,
}, },
{ {
...@@ -8112,36 +8708,59 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) ...@@ -8112,36 +8708,59 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp)
return -EINVAL; return -EINVAL;
} }
module_param(experimental, int, 0); module_param(experimental, int, 0444);
MODULE_PARM_DESC(experimental, MODULE_PARM_DESC(experimental,
"Enables experimental features when non-zero"); "Enables experimental features when non-zero");
module_param_named(debug, dbg_level, uint, 0); module_param_named(debug, dbg_level, uint, 0);
MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
module_param(force_load, bool, 0); module_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load, MODULE_PARM_DESC(force_load,
"Attempts to load the driver even on a " "Attempts to load the driver even on a "
"mis-identified ThinkPad when true"); "mis-identified ThinkPad when true");
module_param_named(fan_control, fan_control_allowed, bool, 0); module_param_named(fan_control, fan_control_allowed, bool, 0444);
MODULE_PARM_DESC(fan_control, MODULE_PARM_DESC(fan_control,
"Enables setting fan parameters features when true"); "Enables setting fan parameters features when true");
module_param_named(brightness_mode, brightness_mode, uint, 0); module_param_named(brightness_mode, brightness_mode, uint, 0444);
MODULE_PARM_DESC(brightness_mode, MODULE_PARM_DESC(brightness_mode,
"Selects brightness control strategy: " "Selects brightness control strategy: "
"0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM"); "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
module_param(brightness_enable, uint, 0); module_param(brightness_enable, uint, 0444);
MODULE_PARM_DESC(brightness_enable, MODULE_PARM_DESC(brightness_enable,
"Enables backlight control when 1, disables when 0"); "Enables backlight control when 1, disables when 0");
module_param(hotkey_report_mode, uint, 0); module_param(hotkey_report_mode, uint, 0444);
MODULE_PARM_DESC(hotkey_report_mode, MODULE_PARM_DESC(hotkey_report_mode,
"used for backwards compatibility with userspace, " "used for backwards compatibility with userspace, "
"see documentation"); "see documentation");
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");
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");
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");
/* 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");
#define TPACPI_PARAM(feature) \ #define TPACPI_PARAM(feature) \
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
...@@ -8160,25 +8779,25 @@ TPACPI_PARAM(volume); ...@@ -8160,25 +8779,25 @@ TPACPI_PARAM(volume);
TPACPI_PARAM(fan); TPACPI_PARAM(fan);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
module_param(dbg_wlswemul, uint, 0); module_param(dbg_wlswemul, uint, 0444);
MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation"); MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0); module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
MODULE_PARM_DESC(wlsw_state, MODULE_PARM_DESC(wlsw_state,
"Initial state of the emulated WLSW switch"); "Initial state of the emulated WLSW switch");
module_param(dbg_bluetoothemul, uint, 0); module_param(dbg_bluetoothemul, uint, 0444);
MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation"); MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0); module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
MODULE_PARM_DESC(bluetooth_state, MODULE_PARM_DESC(bluetooth_state,
"Initial state of the emulated bluetooth switch"); "Initial state of the emulated bluetooth switch");
module_param(dbg_wwanemul, uint, 0); module_param(dbg_wwanemul, uint, 0444);
MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation"); MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0); module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
MODULE_PARM_DESC(wwan_state, MODULE_PARM_DESC(wwan_state,
"Initial state of the emulated WWAN switch"); "Initial state of the emulated WWAN switch");
module_param(dbg_uwbemul, uint, 0); module_param(dbg_uwbemul, uint, 0444);
MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation"); MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0); module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
MODULE_PARM_DESC(uwb_state, MODULE_PARM_DESC(uwb_state,
...@@ -8371,6 +8990,7 @@ static int __init thinkpad_acpi_module_init(void) ...@@ -8371,6 +8990,7 @@ static int __init thinkpad_acpi_module_init(void)
PCI_VENDOR_ID_IBM; PCI_VENDOR_ID_IBM;
tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT; tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION; tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
} }
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
ret = ibm_init(&ibms_init[i]); ret = ibm_init(&ibms_init[i]);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册