Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
Commits
7458bbd0
Commit
7458bbd0
authored
Dec 16, 2009
by
Len Brown
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'thinkpad-2.6.33' into release
parents
1667e399
5d2eb14d
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
963 additions
and
329 deletions
+963
-329
Documentation/laptops/thinkpad-acpi.txt
Documentation/laptops/thinkpad-acpi.txt
+64
-50
drivers/platform/x86/thinkpad_acpi.c
drivers/platform/x86/thinkpad_acpi.c
+899
-279
No files found.
Documentation/laptops/thinkpad-acpi.txt
View file @
7458bbd0
ThinkPad ACPI Extras Driver
Version 0.2
3
April 10th,
2009
Version 0.2
4
December 11th,
2009
Borislav Deianov <borislav@users.sf.net>
Henrique de Moraes Holschuh <hmh@hmh.eng.br>
...
...
@@ -460,6 +460,8 @@ event code Key Notes
For Lenovo ThinkPads with a new
BIOS, it has to be handled either
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
up for details.
...
...
@@ -582,46 +584,15 @@ with hotkey_report_mode.
Brightness hotkey notes:
These are the current sane choices for brightness key mapping in
thinkpad-acpi:
Don't mess with the brightness hotkeys in a Thinkpad. If you want
notifications for OSD, use the sysfs backlight class event support.
For IBM and Lenovo models *without* ACPI backlight control (the ones on
which thinkpad-acpi will autoload its backlight interface by default,
and on which ACPI video does not export a backlight interface):
1. Don't enable or map the brightness hotkeys in thinkpad-acpi, as
these older firmware versions unfortunately won't respect the hotkey
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.
The driver will issue KEY_BRIGHTNESS_UP and KEY_BRIGHTNESS_DOWN events
automatically for the cases were userspace has to do something to
implement brightness changes. When you override these events, you will
either fail to handle properly the ThinkPads that require explicit
action to change backlight brightness, or the ThinkPads that require
that no action be taken to work properly.
Bluetooth
...
...
@@ -1121,25 +1092,61 @@ WARNING:
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
a hardware volume knob. The available commands are:
NOTE: distros are urged to not enable volume_control by default, this
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 down >/proc/acpi/ibm/volume
echo mute >/proc/acpi/ibm/volume
echo unmute >/proc/acpi/ibm/volume
echo 'level <level>' >/proc/acpi/ibm/volume
The <level> number range is 0 to 1
5
although not all of them may be
The <level> number range is 0 to 1
4
although not all of them may be
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 ALSA mixer interface to this feature is still missing, but patches
to add it exist. That problem should be addressed in the not so
distant future.
You can use the volume_capabilities parameter to tell the driver
whether your thinkpad has volume control or mute-only control:
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
...
...
@@ -1405,6 +1412,7 @@ to enable more than one output class, just add their values.
0x0008 HKEY event interface, hotkeys
0x0010 Fan control
0x0020 Backlight brightness
0x0040 Audio mixer/volume control
There is also a kernel build option to enable more debugging
information, which may be necessary to debug driver problems.
...
...
@@ -1465,3 +1473,9 @@ Sysfs interface changelog:
and it is always able to disable hot keys. Very old
thinkpads are properly supported. hotkey_bios_mask
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.
drivers/platform/x86/thinkpad_acpi.c
View file @
7458bbd0
...
...
@@ -21,8 +21,8 @@
* 02110-1301, USA.
*/
#define TPACPI_VERSION "0.2
3
"
#define TPACPI_SYSFS_VERSION 0x020
5
00
#define TPACPI_VERSION "0.2
4
"
#define TPACPI_SYSFS_VERSION 0x020
7
00
/*
* Changelog:
...
...
@@ -61,6 +61,7 @@
#include <linux/nvram.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/sysfs.h>
#include <linux/backlight.h>
#include <linux/fb.h>
...
...
@@ -76,6 +77,10 @@
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <acpi/acpi_drivers.h>
#include <linux/pci_ids.h>
...
...
@@ -231,6 +236,7 @@ enum tpacpi_hkey_event_t {
#define TPACPI_DBG_HKEY 0x0008
#define TPACPI_DBG_FAN 0x0010
#define TPACPI_DBG_BRGHT 0x0020
#define TPACPI_DBG_MIXER 0x0040
#define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
#define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
...
...
@@ -256,7 +262,7 @@ struct tp_acpi_drv_struct {
struct
ibm_struct
{
char
*
name
;
int
(
*
read
)
(
char
*
);
int
(
*
read
)
(
struct
seq_file
*
);
int
(
*
write
)
(
char
*
);
void
(
*
exit
)
(
void
);
void
(
*
resume
)
(
void
);
...
...
@@ -298,6 +304,7 @@ static struct {
u32
fan_ctrl_status_undef
:
1
;
u32
second_fan
:
1
;
u32
beep_needs_two_args
:
1
;
u32
mixer_no_level_control
:
1
;
u32
input_device_registered
:
1
;
u32
platform_drv_registered
:
1
;
u32
platform_drv_attrs_registered
:
1
;
...
...
@@ -309,6 +316,7 @@ static struct {
static
struct
{
u16
hotkey_mask_ff
:
1
;
u16
volume_ctrl_forbidden
:
1
;
}
tp_warned
;
struct
thinkpad_id_data
{
...
...
@@ -425,6 +433,12 @@ static void tpacpi_log_usertask(const char * const what)
.ec = TPACPI_MATCH_ANY, \
.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
{
unsigned
int
vendor
;
u16
bios
;
...
...
@@ -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
,
int
count
,
int
*
eof
,
void
*
data
)
static
int
dispatch_proc_show
(
struct
seq_file
*
m
,
void
*
v
)
{
struct
ibm_struct
*
ibm
=
data
;
int
len
;
struct
ibm_struct
*
ibm
=
m
->
private
;
if
(
!
ibm
||
!
ibm
->
read
)
return
-
EINVAL
;
return
ibm
->
read
(
m
);
}
len
=
ibm
->
read
(
page
);
if
(
len
<
0
)
return
len
;
if
(
len
<=
off
+
count
)
*
eof
=
1
;
*
start
=
page
+
off
;
len
-=
off
;
if
(
len
>
count
)
len
=
count
;
if
(
len
<
0
)
len
=
0
;
return
len
;
static
int
dispatch_proc_open
(
struct
inode
*
inode
,
struct
file
*
file
)
{
return
single_open
(
file
,
dispatch_proc_show
,
PDE
(
inode
)
->
data
);
}
static
int
dispatch_procfs
_write
(
struct
file
*
file
,
static
ssize_t
dispatch_proc
_write
(
struct
file
*
file
,
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
;
int
ret
;
...
...
@@ -834,6 +837,15 @@ static int dispatch_procfs_write(struct file *file,
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
)
{
char
*
start
=
*
cmds
;
...
...
@@ -1261,6 +1273,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
struct
tpacpi_rfk
*
atp_rfk
;
int
res
;
bool
sw_state
=
false
;
bool
hw_state
;
int
sw_status
;
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,
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
);
if
(
res
<
0
)
{
...
...
@@ -1308,6 +1322,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
}
tpacpi_rfkill_switches
[
id
]
=
atp_rfk
;
printk
(
TPACPI_INFO
"rfkill switch %s: radio is %sblocked
\n
"
,
name
,
(
sw_state
||
hw_state
)
?
""
:
"un"
);
return
0
;
}
...
...
@@ -1380,12 +1397,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
}
/* 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
)
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
else
{
int
status
;
...
...
@@ -1399,13 +1414,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p)
return
status
;
}
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
%s
\n
"
,
seq_printf
(
m
,
"status:
\t\t
%s
\n
"
,
(
status
==
TPACPI_RFK_RADIO_ON
)
?
"enabled"
:
"disabled"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
enable, disable
\n
"
);
seq_printf
(
m
,
"commands:
\t
enable, disable
\n
"
);
}
return
len
;
return
0
;
}
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 = {
TPV_QL1
(
'7'
,
'9'
,
'E'
,
'3'
,
'5'
,
'0'
),
/* T60/p */
TPV_QL1
(
'7'
,
'C'
,
'D'
,
'2'
,
'2'
,
'2'
),
/* R60, R60i */
TPV_QL
0
(
'7'
,
'E'
,
'D'
,
'0'
),
/* R60e, R60i */
TPV_QL
1
(
'7'
,
'E'
,
'D'
,
'0'
,
'1'
,
'5'
),
/* R60e, R60i */
/* BIOS FW BIOS VERS EC FW EC VERS */
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 = {
TPV_QI1
(
'7'
,
'4'
,
'6'
,
'4'
,
'2'
,
'7'
),
/* X41 (0) */
TPV_QI1
(
'7'
,
'5'
,
'6'
,
'0'
,
'2'
,
'0'
),
/* X41t (0) */
TPV_QL
0
(
'7'
,
'B'
,
'D'
,
'7'
),
/* X60/s */
TPV_QL
0
(
'7'
,
'J'
,
'3'
,
'0'
),
/* X60t */
TPV_QL
1
(
'7'
,
'B'
,
'D'
,
'7'
,
'4'
,
'0'
),
/* X60/s */
TPV_QL
1
(
'7'
,
'J'
,
'3'
,
'0'
,
'1'
,
'3'
),
/* X60t */
/* (0) - older versions lack DMI EC fw string and functionality */
/* (1) - older versions known to lack functionality */
...
...
@@ -1883,14 +1898,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
return
0
;
}
static
int
thinkpad_acpi_driver_read
(
char
*
p
)
static
int
thinkpad_acpi_driver_read
(
struct
seq_file
*
m
)
{
int
len
=
0
;
len
+=
sprintf
(
p
+
len
,
"driver:
\t\t
%s
\n
"
,
TPACPI_DESC
);
len
+=
sprintf
(
p
+
len
,
"version:
\t
%s
\n
"
,
TPACPI_VERSION
);
return
len
;
seq_printf
(
m
,
"driver:
\t\t
%s
\n
"
,
TPACPI_DESC
);
seq_printf
(
m
,
"version:
\t
%s
\n
"
,
TPACPI_VERSION
);
return
0
;
}
static
struct
ibm_struct
thinkpad_acpi_driver_data
=
{
...
...
@@ -2186,7 +2198,8 @@ static int hotkey_mask_set(u32 mask)
fwmask
,
hotkey_acpi_mask
);
}
hotkey_mask_warn_incomplete_mask
();
if
(
tpacpi_lifecycle
!=
TPACPI_LIFE_EXITING
)
hotkey_mask_warn_incomplete_mask
();
return
rc
;
}
...
...
@@ -3182,6 +3195,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
int
res
,
i
;
int
status
;
int
hkeyv
;
bool
radiosw_state
=
false
;
bool
tabletsw_state
=
false
;
unsigned
long
quirks
;
...
...
@@ -3287,6 +3302,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
if
(
dbg_wlswemul
)
{
tp_features
.
hotkey_wlsw
=
1
;
radiosw_state
=
!!
tpacpi_wlsw_emulstate
;
printk
(
TPACPI_INFO
"radio switch emulation enabled
\n
"
);
}
else
...
...
@@ -3294,6 +3310,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
/* Not all thinkpads have a hardware radio switch */
if
(
acpi_evalf
(
hkey_handle
,
&
status
,
"WLSW"
,
"qd"
))
{
tp_features
.
hotkey_wlsw
=
1
;
radiosw_state
=
!!
status
;
printk
(
TPACPI_INFO
"radio switch found; radios are %s
\n
"
,
enabled
(
status
,
0
));
...
...
@@ -3305,11 +3322,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
/* For X41t, X60t, X61t Tablets... */
if
(
!
res
&&
acpi_evalf
(
hkey_handle
,
&
status
,
"MHKG"
,
"qd"
))
{
tp_features
.
hotkey_tablet
=
1
;
tabletsw_state
=
!!
(
status
&
TP_HOTKEY_TABLET_MASK
);
printk
(
TPACPI_INFO
"possible tablet mode switch found; "
"ThinkPad in %s mode
\n
"
,
(
status
&
TP_HOTKEY_TABLET_MASK
)
?
"tablet"
:
"laptop"
);
(
tabletsw_state
)
?
"tablet"
:
"laptop"
);
res
=
add_to_attr_set
(
hotkey_dev_attributes
,
&
dev_attr_hotkey_tablet_mode
.
attr
);
}
...
...
@@ -3344,16 +3361,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
TPACPI_HOTKEY_MAP_SIZE
);
}
set_bit
(
EV_KEY
,
tpacpi_inputdev
->
evbit
);
set_bit
(
EV_MSC
,
tpacpi_inputdev
->
evbit
);
set_bit
(
MSC_SCAN
,
tpacpi_inputdev
->
mscbit
);
input_set_capability
(
tpacpi_inputdev
,
EV_MSC
,
MSC_SCAN
);
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
)
{
set_bit
(
hotkey_keycode_map
[
i
]
,
tpacpi_inputdev
->
keybit
);
input_set_capability
(
tpacpi_inputdev
,
EV_KEY
,
hotkey_keycode_map
[
i
]
);
}
else
{
if
(
i
<
sizeof
(
hotkey_reserved_mask
)
*
8
)
hotkey_reserved_mask
|=
1
<<
i
;
...
...
@@ -3361,12 +3376,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
}
if
(
tp_features
.
hotkey_wlsw
)
{
set_bit
(
EV_SW
,
tpacpi_inputdev
->
evbit
);
set_bit
(
SW_RFKILL_ALL
,
tpacpi_inputdev
->
swbit
);
input_set_capability
(
tpacpi_inputdev
,
EV_SW
,
SW_RFKILL_ALL
);
input_report_switch
(
tpacpi_inputdev
,
SW_RFKILL_ALL
,
radiosw_state
);
}
if
(
tp_features
.
hotkey_tablet
)
{
set_bit
(
EV_SW
,
tpacpi_inputdev
->
evbit
);
set_bit
(
SW_TABLET_MODE
,
tpacpi_inputdev
->
swbit
);
input_set_capability
(
tpacpi_inputdev
,
EV_SW
,
SW_TABLET_MODE
);
input_report_switch
(
tpacpi_inputdev
,
SW_TABLET_MODE
,
tabletsw_state
);
}
/* Do not issue duplicate brightness change events to
...
...
@@ -3433,8 +3450,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
tpacpi_inputdev
->
close
=
&
hotkey_inputdev_close
;
hotkey_poll_setup_safe
(
true
);
tpacpi_send_radiosw_update
();
tpacpi_input_send_tabletsw
();
return
0
;
...
...
@@ -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
,
bool
*
send_acpi_ev
,
bool
*
ignore_acpi_ev
)
{
bool
known
=
true
;
/* 0x6000-0x6FFF: thermal alarms */
*
send_acpi_ev
=
true
;
*
ignore_acpi_ev
=
false
;
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
:
printk
(
TPACPI_CRIT
"THERMAL ALARM: battery is too hot!
\n
"
);
/* recommended action: warn user through gui */
return
true
;
break
;
case
TP_HKEY_EV_ALARM_BAT_XHOT
:
printk
(
TPACPI_ALERT
"THERMAL EMERGENCY: battery is extremely hot!
\n
"
);
/* recommended action: immediate sleep/hibernate */
return
true
;
break
;
case
TP_HKEY_EV_ALARM_SENSOR_HOT
:
printk
(
TPACPI_CRIT
"THERMAL ALARM: "
"a sensor reports something is too hot!
\n
"
);
/* recommended action: warn user through gui, that */
/* some internal component is too hot */
return
true
;
break
;
case
TP_HKEY_EV_ALARM_SENSOR_XHOT
:
printk
(
TPACPI_ALERT
"THERMAL EMERGENCY: "
"a sensor reports something is extremely hot!
\n
"
);
/* recommended action: immediate sleep/hibernate */
return
true
;
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
;
break
;
default:
printk
(
TPACPI_ALERT
"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
)
...
...
@@ -3727,14 +3750,13 @@ static void hotkey_resume(void)
}
/* procfs -------------------------------------------------------------- */
static
int
hotkey_read
(
char
*
p
)
static
int
hotkey_read
(
struct
seq_file
*
m
)
{
int
res
,
status
;
int
len
=
0
;
if
(
!
tp_features
.
hotkey
)
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
return
len
;
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
return
0
;
}
if
(
mutex_lock_killable
(
&
hotkey_mutex
))
...
...
@@ -3746,17 +3768,16 @@ static int hotkey_read(char *p)
if
(
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
)
{
len
+=
sprintf
(
p
+
len
,
"mask:
\t\t
0x%08x
\n
"
,
hotkey_user_mask
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
enable, disable, reset, <mask>
\n
"
);
seq_printf
(
m
,
"mask:
\t\t
0x%08x
\n
"
,
hotkey_user_mask
);
seq_printf
(
m
,
"commands:
\t
enable, disable, reset, <mask>
\n
"
);
}
else
{
len
+=
sprintf
(
p
+
len
,
"mask:
\t\t
not supported
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
enable, disable, reset
\n
"
);
seq_printf
(
m
,
"mask:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"commands:
\t
enable, disable, reset
\n
"
);
}
return
len
;
return
0
;
}
static
void
hotkey_enabledisable_warn
(
bool
enable
)
...
...
@@ -3863,15 +3884,6 @@ enum {
#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
)
{
int
status
;
...
...
@@ -3905,10 +3917,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state)
#endif
/* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */
status
=
TP_ACPI_BLUETOOTH_RESUMECTRL
;
if
(
state
==
TPACPI_RFK_RADIO_ON
)
status
=
TP_ACPI_BLUETOOTH_RADIOSSW
;
else
status
=
0
;
status
|=
TP_ACPI_BLUETOOTH_RADIOSSW
;
if
(
!
acpi_evalf
(
hkey_handle
,
NULL
,
"SBDC"
,
"vd"
,
status
))
return
-
EIO
;
...
...
@@ -4032,9 +4043,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm)
}
/* 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
)
...
...
@@ -4047,7 +4058,6 @@ static struct ibm_struct bluetooth_driver_data = {
.
read
=
bluetooth_read
,
.
write
=
bluetooth_write
,
.
exit
=
bluetooth_exit
,
.
suspend
=
bluetooth_suspend
,
.
shutdown
=
bluetooth_shutdown
,
};
...
...
@@ -4065,15 +4075,6 @@ enum {
#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
)
{
int
status
;
...
...
@@ -4106,11 +4107,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state)
}
#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
)
status
=
TP_ACPI_WANCARD_RADIOSSW
;
else
status
=
0
;
status
|=
TP_ACPI_WANCARD_RADIOSSW
;
if
(
!
acpi_evalf
(
hkey_handle
,
NULL
,
"SWAN"
,
"vd"
,
status
))
return
-
EIO
;
...
...
@@ -4233,9 +4233,9 @@ static int __init wan_init(struct ibm_init_struct *iibm)
}
/* 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
)
...
...
@@ -4248,7 +4248,6 @@ static struct ibm_struct wan_driver_data = {
.
read
=
wan_read
,
.
write
=
wan_write
,
.
exit
=
wan_exit
,
.
suspend
=
wan_suspend
,
.
shutdown
=
wan_shutdown
,
};
...
...
@@ -4611,14 +4610,13 @@ static int video_expand_toggle(void)
/* not reached */
}
static
int
video_read
(
char
*
p
)
static
int
video_read
(
struct
seq_file
*
m
)
{
int
status
,
autosw
;
int
len
=
0
;
if
(
video_supported
==
TPACPI_VIDEO_NONE
)
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
return
len
;
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
return
0
;
}
status
=
video_outputsw_get
();
...
...
@@ -4629,20 +4627,20 @@ static int video_read(char *p)
if
(
autosw
<
0
)
return
autosw
;
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
supported
\n
"
);
len
+=
sprintf
(
p
+
len
,
"lcd:
\t\t
%s
\n
"
,
enabled
(
status
,
0
));
len
+=
sprintf
(
p
+
len
,
"crt:
\t\t
%s
\n
"
,
enabled
(
status
,
1
));
seq_printf
(
m
,
"status:
\t\t
supported
\n
"
);
seq_printf
(
m
,
"lcd:
\t\t
%s
\n
"
,
enabled
(
status
,
0
));
seq_printf
(
m
,
"crt:
\t\t
%s
\n
"
,
enabled
(
status
,
1
));
if
(
video_supported
==
TPACPI_VIDEO_NEW
)
len
+=
sprintf
(
p
+
len
,
"dvi:
\t\t
%s
\n
"
,
enabled
(
status
,
3
));
len
+=
sprintf
(
p
+
len
,
"auto:
\t\t
%s
\n
"
,
enabled
(
autosw
,
0
));
len
+=
sprintf
(
p
+
len
,
"commands:
\t
lcd_enable, lcd_disable
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
crt_enable, crt_disable
\n
"
);
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:
\t
lcd_enable, lcd_disable
\n
"
);
seq_printf
(
m
,
"commands:
\t
crt_enable, crt_disable
\n
"
);
if
(
video_supported
==
TPACPI_VIDEO_NEW
)
len
+=
sprintf
(
p
+
len
,
"commands:
\t
dvi_enable, dvi_disable
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
auto_enable, auto_disable
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
video_switch, expand_toggle
\n
"
);
seq_printf
(
m
,
"commands:
\t
dvi_enable, dvi_disable
\n
"
);
seq_printf
(
m
,
"commands:
\t
auto_enable, auto_disable
\n
"
);
seq_printf
(
m
,
"commands:
\t
video_switch, expand_toggle
\n
"
);
return
len
;
return
0
;
}
static
int
video_write
(
char
*
buf
)
...
...
@@ -4834,25 +4832,24 @@ static void light_exit(void)
flush_workqueue
(
tpacpi_wq
);
}
static
int
light_read
(
char
*
p
)
static
int
light_read
(
struct
seq_file
*
m
)
{
int
len
=
0
;
int
status
;
if
(
!
tp_features
.
light
)
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
}
else
if
(
!
tp_features
.
light_status
)
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
unknown
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
on, off
\n
"
);
seq_printf
(
m
,
"status:
\t\t
unknown
\n
"
);
seq_printf
(
m
,
"commands:
\t
on, off
\n
"
);
}
else
{
status
=
light_get_status
();
if
(
status
<
0
)
return
status
;
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
%s
\n
"
,
onoff
(
status
,
0
));
len
+=
sprintf
(
p
+
len
,
"commands:
\t
on, off
\n
"
);
seq_printf
(
m
,
"status:
\t\t
%s
\n
"
,
onoff
(
status
,
0
));
seq_printf
(
m
,
"commands:
\t
on, off
\n
"
);
}
return
len
;
return
0
;
}
static
int
light_write
(
char
*
buf
)
...
...
@@ -4930,20 +4927,18 @@ static void cmos_exit(void)
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,
R30, R31, T20-22, X20-21 */
if
(
!
cmos_handle
)
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
else
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
supported
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
<cmd> (<cmd> is 0-21)
\n
"
);
seq_printf
(
m
,
"status:
\t\t
supported
\n
"
);
seq_printf
(
m
,
"commands:
\t
<cmd> (<cmd> is 0-21)
\n
"
);
}
return
len
;
return
0
;
}
static
int
cmos_write
(
char
*
buf
)
...
...
@@ -5318,15 +5313,13 @@ static int __init led_init(struct ibm_init_struct *iibm)
((s) == TPACPI_LED_OFF ? "off" : \
((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
)
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
return
len
;
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
return
0
;
}
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
supported
\n
"
);
if
(
led_supported
==
TPACPI_LED_570
)
{
/* 570 */
...
...
@@ -5335,15 +5328,15 @@ static int led_read(char *p)
status
=
led_get_status
(
i
);
if
(
status
<
0
)
return
-
EIO
;
len
+=
sprintf
(
p
+
len
,
"%d:
\t\t
%s
\n
"
,
seq_printf
(
m
,
"%d:
\t\t
%s
\n
"
,
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
"
);
return
len
;
return
0
;
}
static
int
led_write
(
char
*
buf
)
...
...
@@ -5416,18 +5409,16 @@ static int __init beep_init(struct ibm_init_struct *iibm)
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
)
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
else
{
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
supported
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
<cmd> (<cmd> is 0-17)
\n
"
);
seq_printf
(
m
,
"status:
\t\t
supported
\n
"
);
seq_printf
(
m
,
"commands:
\t
<cmd> (<cmd> is 0-17)
\n
"
);
}
return
len
;
return
0
;
}
static
int
beep_write
(
char
*
buf
)
...
...
@@ -5480,8 +5471,11 @@ 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 */
TPACPI_THERMAL_SENSOR_NA
=
-
128000
,
/* Sensor not available */
};
#define TPACPI_MAX_THERMAL_SENSORS 16
/* Max thermal sensors supported */
struct
ibm_thermal_sensors_struct
{
s32
temp
[
TPACPI_MAX_THERMAL_SENSORS
];
...
...
@@ -5571,6 +5565,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
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 -------------------------------------------------- */
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
);
if
(
res
)
return
res
;
if
(
value
==
TP
_EC_THERMAL_TMP_NA
*
1000
)
if
(
value
==
TP
ACPI_THERMAL_SENSOR_NA
)
return
-
ENXIO
;
return
snprintf
(
buf
,
PAGE_SIZE
,
"%d
\n
"
,
value
);
...
...
@@ -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
;
struct
ibm_thermal_sensors_struct
t
;
...
...
@@ -5773,16 +5788,16 @@ static int thermal_read(char *p)
if
(
unlikely
(
n
<
0
))
return
n
;
len
+=
sprintf
(
p
+
len
,
"temperatures:
\t
"
);
seq_printf
(
m
,
"temperatures:
\t
"
);
if
(
n
>
0
)
{
for
(
i
=
0
;
i
<
(
n
-
1
);
i
++
)
len
+=
sprintf
(
p
+
len
,
"%d "
,
t
.
temp
[
i
]
/
1000
);
len
+=
sprintf
(
p
+
len
,
"%d
\n
"
,
t
.
temp
[
i
]
/
1000
);
seq_printf
(
m
,
"%d "
,
t
.
temp
[
i
]
/
1000
);
seq_printf
(
m
,
"%d
\n
"
,
t
.
temp
[
i
]
/
1000
);
}
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
=
{
...
...
@@ -5797,39 +5812,38 @@ static struct ibm_struct thermal_driver_data = {
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
;
u8
v
;
len
+=
sprintf
(
p
+
len
,
"EC "
seq_printf
(
m
,
"EC "
" +00 +01 +02 +03 +04 +05 +06 +07"
" +08 +09 +0a +0b +0c +0d +0e +0f
\n
"
);
for
(
i
=
0
;
i
<
256
;
i
+=
16
)
{
len
+=
sprintf
(
p
+
len
,
"EC 0x%02x:"
,
i
);
seq_printf
(
m
,
"EC 0x%02x:"
,
i
);
for
(
j
=
0
;
j
<
16
;
j
++
)
{
if
(
!
acpi_ec_read
(
i
+
j
,
&
v
))
break
;
if
(
v
!=
ecdump_regs
[
i
+
j
])
len
+=
sprintf
(
p
+
len
,
" *%02x"
,
v
);
seq_printf
(
m
,
" *%02x"
,
v
);
else
len
+=
sprintf
(
p
+
len
,
" %02x"
,
v
);
seq_printf
(
m
,
" %02x"
,
v
);
ecdump_regs
[
i
+
j
]
=
v
;
}
len
+=
sprintf
(
p
+
len
,
"
\n
"
);
seq_putc
(
m
,
'\n'
);
if
(
j
!=
16
)
break
;
}
/* These are way too dangerous to advertise openly... */
#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");
len += sprintf(p + len
, "commands:\t0x<offset> <value> "
seq_printf(m
, "commands:\t0x<offset> <value> "
" (<offset> is 00-ff, <value> is 0-255)\n");
#endif
return
len
;
return
0
;
}
static
int
ecdump_write
(
char
*
buf
)
...
...
@@ -6092,6 +6106,12 @@ static int brightness_get(struct backlight_device *bd)
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
=
{
.
get_brightness
=
brightness_get
,
.
update_status
=
brightness_update_status
,
...
...
@@ -6120,8 +6140,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
/* Models with Intel Extreme Graphics 2 */
TPACPI_Q_IBM
(
'1'
,
'U'
,
TPACPI_BRGHT_Q_NOEC
),
TPACPI_Q_IBM
(
'1'
,
'V'
,
TPACPI_BRGHT_Q_ASK
|
TPACPI_BRGHT_Q_
NO
EC
),
TPACPI_Q_IBM
(
'1'
,
'W'
,
TPACPI_BRGHT_Q_ASK
|
TPACPI_BRGHT_Q_
NO
EC
),
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
),
/* Models with Intel GMA900 */
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)
ibm_backlight_device
->
props
.
brightness
=
b
&
TP_EC_BACKLIGHT_LVLMSK
;
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
;
}
...
...
@@ -6270,23 +6296,22 @@ static void brightness_exit(void)
tpacpi_brightness_checkpoint_nvram
();
}
static
int
brightness_read
(
char
*
p
)
static
int
brightness_read
(
struct
seq_file
*
m
)
{
int
len
=
0
;
int
level
;
level
=
brightness_get
(
NULL
);
if
(
level
<
0
)
{
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
unreadable
\n
"
);
seq_printf
(
m
,
"level:
\t\t
unreadable
\n
"
);
}
else
{
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
%d
\n
"
,
level
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
up, down
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
level <level>"
seq_printf
(
m
,
"level:
\t\t
%d
\n
"
,
level
);
seq_printf
(
m
,
"commands:
\t
up, down
\n
"
);
seq_printf
(
m
,
"commands:
\t
level <level>"
" (<level> is 0-%d)
\n
"
,
(
tp_features
.
bright_16levels
)
?
15
:
7
);
}
return
len
;
return
0
;
}
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
*/
rc
=
brightness_set
(
level
);
if
(
!
rc
&&
ibm_backlight_device
)
backlight_force_update
(
ibm_backlight_device
,
BACKLIGHT_UPDATE_SYSFS
);
return
(
rc
==
-
EINTR
)
?
-
ERESTARTSYS
:
rc
;
}
...
...
@@ -6338,99 +6366,654 @@ static struct ibm_struct brightness_driver_data = {
* 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
,
/* 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
;
static
int
volume_read
(
char
*
p
)
/*
* 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
level
;
u8
lec
=
0
;
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
(
!
acpi_ec_read
(
volume_offset
,
&
level
))
{
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
unreadable
\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
(
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
);
}
else
{
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
%d
\n
"
,
level
&
0xf
);
len
+=
sprintf
(
p
+
len
,
"mute:
\t\t
%s
\n
"
,
onoff
(
level
,
6
));
len
+=
sprintf
(
p
+
len
,
"commands:
\t
up, down, mute
\n
"
);
len
+=
sprintf
(
p
+
len
,
"commands:
\t
level <level>"
" (<level> is 0-15)
\n
"
);
vdbg_printk
(
TPACPI_DBG_MIXER
,
"NVRAM mixer status already is 0x%02x (0x%02x)
\n
"
,
(
unsigned
int
)
lec
,
(
unsigned
int
)
b_nvram
);
}
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
level
,
mute
;
int
new_level
,
new_mute
;
char
*
cmd
;
u8
s
;
while
((
cmd
=
next_cmd
(
&
buf
)))
{
if
(
!
acpi_ec_read
(
volume_offset
,
&
level
))
return
-
EIO
;
new_mute
=
mute
=
level
&
0x40
;
new_level
=
level
=
level
&
0xf
;
if
(
!
acpi_ec_read
(
TP_EC_AUDIO
,
&
s
))
return
-
EIO
;
if
(
strlencmp
(
cmd
,
"up"
)
==
0
)
{
if
(
mute
)
new_mute
=
0
;
else
new_level
=
level
==
15
?
15
:
level
+
1
;
}
else
if
(
strlencmp
(
cmd
,
"down"
)
==
0
)
{
if
(
mute
)
new_mute
=
0
;
else
new_level
=
level
==
0
?
0
:
level
-
1
;
}
else
if
(
sscanf
(
cmd
,
"level %d"
,
&
new_level
)
==
1
&&
new_level
>=
0
&&
new_level
<=
15
)
{
/* new_level set */
}
else
if
(
strlencmp
(
cmd
,
"mute"
)
==
0
)
{
new_mute
=
0x40
;
}
else
return
-
EINVAL
;
*
status
=
s
;
if
(
new_level
!=
level
)
{
/* mute doesn't change */
dbg_printk
(
TPACPI_DBG_MIXER
,
"status 0x%02x
\n
"
,
s
);
cmos_cmd
=
(
new_level
>
level
)
?
TP_CMOS_VOLUME_UP
:
TP_CMOS_VOLUME_DOWN
;
inc
=
new_level
>
level
?
1
:
-
1
;
return
0
;
}
if
(
mute
&&
(
issue_thinkpad_cmos_command
(
cmos_cmd
)
||
!
acpi_ec_write
(
volume_offset
,
level
)))
return
-
EIO
;
static
int
volume_get_status
(
u8
*
status
)
{
return
volume_get_status_ec
(
status
);
}
for
(
i
=
level
;
i
!=
new_level
;
i
+=
inc
)
if
(
issue_thinkpad_cmos_command
(
cmos_cmd
)
||
!
acpi_ec_write
(
volume_offset
,
i
+
inc
))
return
-
EIO
;
static
int
volume_set_status_ec
(
const
u8
status
)
{
if
(
!
acpi_ec_write
(
TP_EC_AUDIO
,
status
))
return
-
EIO
;
if
(
mute
&&
(
issue_thinkpad_cmos_command
(
TP_CMOS_VOLUME_MUTE
)
||
!
acpi_ec_write
(
volume_offset
,
new_level
+
mute
)))
{
return
-
EIO
;
}
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
);
}
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
(
!
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
)
{
printk
(
TPACPI_ERR
"Failed to create ALSA volume control
\n
"
);
goto
err_out
;
}
data
->
ctl_vol_id
=
&
ctl_vol
->
id
;
}
if
(
new_mute
!=
mute
)
{
/* level doesn't change */
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
)
?
TP_CMOS_VOLUME_MUTE
:
TP_CMOS_VOLUME_UP
;
snd_card_set_dev
(
card
,
&
tpacpi_pdev
->
dev
);
rc
=
snd_card_register
(
card
)
;
if
(
issue_thinkpad_cmos_command
(
cmos_cmd
)
||
!
acpi_ec_write
(
volume_offset
,
level
+
new_mute
))
return
-
EIO
;
err_out:
if
(
rc
<
0
)
{
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\t
unreadable
\n
"
);
}
else
{
if
(
tp_features
.
mixer_no_level_control
)
seq_printf
(
m
,
"level:
\t\t
unsupported
\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:
\t
unmute, mute
\n
"
);
if
(
!
tp_features
.
mixer_no_level_control
)
{
seq_printf
(
m
,
"commands:
\t
up, down
\n
"
);
seq_printf
(
m
,
"commands:
\t
level <level>"
" (<level> is 0-%d)
\n
"
,
TP_EC_VOLUME_MAX
);
}
}
}
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
=
{
.
name
=
"volume"
,
.
read
=
volume_read
,
.
write
=
volume_write
,
.
exit
=
volume_exit
,
.
suspend
=
volume_suspend
,
.
resume
=
volume_resume
,
.
shutdown
=
volume_shutdown
,
};
/*************************************************************************
...
...
@@ -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
;
u8
status
;
unsigned
int
speed
=
0
;
...
...
@@ -7521,7 +8103,7 @@ static int fan_read(char *p)
if
(
rc
<
0
)
return
rc
;
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
%s
\n
"
seq_printf
(
m
,
"status:
\t\t
%s
\n
"
"level:
\t\t
%d
\n
"
,
(
status
!=
0
)
?
"enabled"
:
"disabled"
,
status
);
break
;
...
...
@@ -7532,54 +8114,54 @@ static int fan_read(char *p)
if
(
rc
<
0
)
return
rc
;
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
%s
\n
"
,
seq_printf
(
m
,
"status:
\t\t
%s
\n
"
,
(
status
!=
0
)
?
"enabled"
:
"disabled"
);
rc
=
fan_get_speed
(
&
speed
);
if
(
rc
<
0
)
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
)
/* Disengaged mode takes precedence */
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
disengaged
\n
"
);
seq_printf
(
m
,
"level:
\t\t
disengaged
\n
"
);
else
if
(
status
&
TP_EC_FAN_AUTO
)
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
auto
\n
"
);
seq_printf
(
m
,
"level:
\t\t
auto
\n
"
);
else
len
+=
sprintf
(
p
+
len
,
"level:
\t\t
%d
\n
"
,
status
);
seq_printf
(
m
,
"level:
\t\t
%d
\n
"
,
status
);
break
;
case
TPACPI_FAN_NONE
:
default:
len
+=
sprintf
(
p
+
len
,
"status:
\t\t
not supported
\n
"
);
seq_printf
(
m
,
"status:
\t\t
not supported
\n
"
);
}
if
(
fan_control_commands
&
TPACPI_FAN_CMD_LEVEL
)
{
len
+=
sprintf
(
p
+
len
,
"commands:
\t
level <level>"
);
seq_printf
(
m
,
"commands:
\t
level <level>"
);
switch
(
fan_control_access_mode
)
{
case
TPACPI_FAN_WR_ACPI_SFAN
:
len
+=
sprintf
(
p
+
len
,
" (<level> is 0-7)
\n
"
);
seq_printf
(
m
,
" (<level> is 0-7)
\n
"
);
break
;
default:
len
+=
sprintf
(
p
+
len
,
" (<level> is 0-7, "
seq_printf
(
m
,
" (<level> is 0-7, "
"auto, disengaged, full-speed)
\n
"
);
break
;
}
}
if
(
fan_control_commands
&
TPACPI_FAN_CMD_ENABLE
)
len
+=
sprintf
(
p
+
len
,
"commands:
\t
enable, disable
\n
"
seq_printf
(
m
,
"commands:
\t
enable, disable
\n
"
"commands:
\t
watchdog <timeout> (<timeout> "
"is 0 (off), 1-120 (seconds))
\n
"
);
if
(
fan_control_commands
&
TPACPI_FAN_CMD_SPEED
)
len
+=
sprintf
(
p
+
len
,
"commands:
\t
speed <speed>"
seq_printf
(
m
,
"commands:
\t
speed <speed>"
" (<speed> is 0-65535)
\n
"
);
return
len
;
return
0
;
}
static
int
fan_write_cmd_level
(
const
char
*
cmd
,
int
*
rc
)
...
...
@@ -7721,10 +8303,23 @@ static struct ibm_struct fan_driver_data = {
*/
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
)
{
tpacpi_driver_event
(
TP_HKEY_EV_HOTKEY_BASE
+
scancode
);
...
...
@@ -7853,19 +8448,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
"%s installed
\n
"
,
ibm
->
name
);
if
(
ibm
->
read
)
{
entry
=
create_proc_entry
(
ibm
->
name
,
S_IFREG
|
S_IRUGO
|
S_IWUSR
,
proc_dir
);
mode_t
mode
;
mode
=
S_IRUGO
;
if
(
ibm
->
write
)
mode
|=
S_IWUSR
;
entry
=
proc_create_data
(
ibm
->
name
,
mode
,
proc_dir
,
&
dispatch_proc_fops
,
ibm
);
if
(
!
entry
)
{
printk
(
TPACPI_ERR
"unable to create proc entry %s
\n
"
,
ibm
->
name
);
ret
=
-
ENODEV
;
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
;
}
...
...
@@ -8077,6 +8672,7 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.
data
=
&
brightness_driver_data
,
},
{
.
init
=
volume_init
,
.
data
=
&
volume_driver_data
,
},
{
...
...
@@ -8112,36 +8708,59 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp)
return
-
EINVAL
;
}
module_param
(
experimental
,
int
,
0
);
module_param
(
experimental
,
int
,
0
444
);
MODULE_PARM_DESC
(
experimental
,
"Enables experimental features when non-zero"
);
module_param_named
(
debug
,
dbg_level
,
uint
,
0
);
MODULE_PARM_DESC
(
debug
,
"Sets debug level bit-mask"
);
module_param
(
force_load
,
bool
,
0
);
module_param
(
force_load
,
bool
,
0
444
);
MODULE_PARM_DESC
(
force_load
,
"Attempts to load the driver even on a "
"mis-identified ThinkPad when true"
);
module_param_named
(
fan_control
,
fan_control_allowed
,
bool
,
0
);
module_param_named
(
fan_control
,
fan_control_allowed
,
bool
,
0
444
);
MODULE_PARM_DESC
(
fan_control
,
"Enables setting fan parameters features when true"
);
module_param_named
(
brightness_mode
,
brightness_mode
,
uint
,
0
);
module_param_named
(
brightness_mode
,
brightness_mode
,
uint
,
0
444
);
MODULE_PARM_DESC
(
brightness_mode
,
"Selects brightness control strategy: "
"0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM"
);
module_param
(
brightness_enable
,
uint
,
0
);
module_param
(
brightness_enable
,
uint
,
0
444
);
MODULE_PARM_DESC
(
brightness_enable
,
"Enables backlight control when 1, disables when 0"
);
module_param
(
hotkey_report_mode
,
uint
,
0
);
module_param
(
hotkey_report_mode
,
uint
,
0
444
);
MODULE_PARM_DESC
(
hotkey_report_mode
,
"used for backwards compatibility with userspace, "
"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) \
module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
...
...
@@ -8160,25 +8779,25 @@ TPACPI_PARAM(volume);
TPACPI_PARAM
(
fan
);
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
module_param
(
dbg_wlswemul
,
uint
,
0
);
module_param
(
dbg_wlswemul
,
uint
,
0
444
);
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"
);
module_param
(
dbg_bluetoothemul
,
uint
,
0
);
module_param
(
dbg_bluetoothemul
,
uint
,
0
444
);
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"
);
module_param
(
dbg_wwanemul
,
uint
,
0
);
module_param
(
dbg_wwanemul
,
uint
,
0
444
);
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"
);
module_param
(
dbg_uwbemul
,
uint
,
0
);
module_param
(
dbg_uwbemul
,
uint
,
0
444
);
MODULE_PARM_DESC
(
dbg_uwbemul
,
"Enables UWB switch emulation"
);
module_param_named
(
uwb_state
,
tpacpi_uwb_emulstate
,
bool
,
0
);
MODULE_PARM_DESC
(
uwb_state
,
...
...
@@ -8371,6 +8990,7 @@ static int __init thinkpad_acpi_module_init(void)
PCI_VENDOR_ID_IBM
;
tpacpi_inputdev
->
id
.
product
=
TPACPI_HKEY_INPUT_PRODUCT
;
tpacpi_inputdev
->
id
.
version
=
TPACPI_HKEY_INPUT_VERSION
;
tpacpi_inputdev
->
dev
.
parent
=
&
tpacpi_pdev
->
dev
;
}
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
ibms_init
);
i
++
)
{
ret
=
ibm_init
(
&
ibms_init
[
i
]);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment