Merge branch 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86
* 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86: (32 commits) Move N014, N051 and CR620 dmi information to load scm dmi table drivers/platform/x86/eeepc-wmi.c: fix build warning X86 platfrom wmi: Add debug facility to dump WMI data in a readable way X86 platform wmi: Also log GUID string when an event happens and debug is set X86 platform wmi: Introduce debug param to log all WMI events Clean up all objects used by scm model when driver initial fail or exit msi-laptop: fix up some coding style issues found by checkpatch msi-laptop: Add i8042 filter to sync sw state with BIOS when function key pressed msi-laptop: Set rfkill init state when msi-laptop intiial msi-laptop: Add MSI CR620 notebook dmi information to scm models table msi-laptop: Add N014 N051 dmi information to scm models table drivers/platform/x86: Use kmemdup drivers/platform/x86: Use kzalloc drivers/platform/x86: Clarify the MRST IPC driver description slightly eeepc-wmi: depends on BACKLIGHT_CLASS_DEVICE IPC driver for Intel Mobile Internet Device (MID) platforms classmate-laptop: Add RFKILL support. thinkpad-acpi: document backlight level writeback at driver init thinkpad-acpi: clean up ACPI handles handling thinkpad-acpi: don't depend on led_path for led firmware type (v2) ...
This commit is contained in:
Коммит
27a3353a45
|
@ -292,13 +292,13 @@ sysfs notes:
|
|||
|
||||
Warning: when in NVRAM mode, the volume up/down/mute
|
||||
keys are synthesized according to changes in the mixer,
|
||||
so you have to use volume up or volume down to unmute,
|
||||
as per the ThinkPad volume mixer user interface. When
|
||||
in ACPI event mode, volume up/down/mute are reported as
|
||||
separate events, but this behaviour may be corrected in
|
||||
future releases of this driver, in which case the
|
||||
ThinkPad volume mixer user interface semantics will be
|
||||
enforced.
|
||||
which uses a single volume up or volume down hotkey
|
||||
press to unmute, as per the ThinkPad volume mixer user
|
||||
interface. When in ACPI event mode, volume up/down/mute
|
||||
events are reported by the firmware and can behave
|
||||
differently (and that behaviour changes with firmware
|
||||
version -- not just with firmware models -- as well as
|
||||
OSI(Linux) state).
|
||||
|
||||
hotkey_poll_freq:
|
||||
frequency in Hz for hot key polling. It must be between
|
||||
|
@ -309,7 +309,7 @@ sysfs notes:
|
|||
will cause hot key presses that require NVRAM polling
|
||||
to never be reported.
|
||||
|
||||
Setting hotkey_poll_freq too low will cause repeated
|
||||
Setting hotkey_poll_freq too low may cause repeated
|
||||
pressings of the same hot key to be misreported as a
|
||||
single key press, or to not even be detected at all.
|
||||
The recommended polling frequency is 10Hz.
|
||||
|
@ -397,6 +397,7 @@ ACPI Scan
|
|||
event code Key Notes
|
||||
|
||||
0x1001 0x00 FN+F1 -
|
||||
|
||||
0x1002 0x01 FN+F2 IBM: battery (rare)
|
||||
Lenovo: Screen lock
|
||||
|
||||
|
@ -404,7 +405,8 @@ event code Key Notes
|
|||
this hot key, even with hot keys
|
||||
disabled or with Fn+F3 masked
|
||||
off
|
||||
IBM: screen lock
|
||||
IBM: screen lock, often turns
|
||||
off the ThinkLight as side-effect
|
||||
Lenovo: battery
|
||||
|
||||
0x1004 0x03 FN+F4 Sleep button (ACPI sleep button
|
||||
|
@ -433,7 +435,8 @@ event code Key Notes
|
|||
Do you feel lucky today?
|
||||
|
||||
0x1008 0x07 FN+F8 IBM: toggle screen expand
|
||||
Lenovo: configure UltraNav
|
||||
Lenovo: configure UltraNav,
|
||||
or toggle screen expand
|
||||
|
||||
0x1009 0x08 FN+F9 -
|
||||
.. .. ..
|
||||
|
@ -444,7 +447,7 @@ event code Key Notes
|
|||
either through the ACPI event,
|
||||
or through a hotkey event.
|
||||
The firmware may refuse to
|
||||
generate further FN+F4 key
|
||||
generate further FN+F12 key
|
||||
press events until a S3 or S4
|
||||
ACPI sleep cycle is performed,
|
||||
or some time passes.
|
||||
|
@ -512,15 +515,19 @@ events for switches:
|
|||
SW_RFKILL_ALL T60 and later hardware rfkill rocker switch
|
||||
SW_TABLET_MODE Tablet ThinkPads HKEY events 0x5009 and 0x500A
|
||||
|
||||
Non hot-key ACPI HKEY event map:
|
||||
Non hotkey ACPI HKEY event map:
|
||||
-------------------------------
|
||||
|
||||
Events that are not propagated by the driver, except for legacy
|
||||
compatibility purposes when hotkey_report_mode is set to 1:
|
||||
|
||||
0x5001 Lid closed
|
||||
0x5002 Lid opened
|
||||
0x5009 Tablet swivel: switched to tablet mode
|
||||
0x500A Tablet swivel: switched to normal mode
|
||||
0x7000 Radio Switch may have changed state
|
||||
|
||||
The above events are not propagated by the driver, except for legacy
|
||||
compatibility purposes when hotkey_report_mode is set to 1.
|
||||
Events that are never propagated by the driver:
|
||||
|
||||
0x2304 System is waking up from suspend to undock
|
||||
0x2305 System is waking up from suspend to eject bay
|
||||
|
@ -528,14 +535,39 @@ compatibility purposes when hotkey_report_mode is set to 1.
|
|||
0x2405 System is waking up from hibernation to eject bay
|
||||
0x5010 Brightness level changed/control event
|
||||
|
||||
The above events are never propagated by the driver.
|
||||
Events that are propagated by the driver to userspace:
|
||||
|
||||
0x2313 ALARM: System is waking up from suspend because
|
||||
the battery is nearly empty
|
||||
0x2413 ALARM: System is waking up from hibernation because
|
||||
the battery is nearly empty
|
||||
0x3003 Bay ejection (see 0x2x05) complete, can sleep again
|
||||
0x3006 Bay hotplug request (hint to power up SATA link when
|
||||
the optical drive tray is ejected)
|
||||
0x4003 Undocked (see 0x2x04), can sleep again
|
||||
0x500B Tablet pen inserted into its storage bay
|
||||
0x500C Tablet pen removed from its storage bay
|
||||
0x6011 ALARM: battery is too hot
|
||||
0x6012 ALARM: battery is extremely hot
|
||||
0x6021 ALARM: a sensor is too hot
|
||||
0x6022 ALARM: a sensor is extremely hot
|
||||
0x6030 System thermal table changed
|
||||
|
||||
The above events are propagated by the driver.
|
||||
Battery nearly empty alarms are a last resort attempt to get the
|
||||
operating system to hibernate or shutdown cleanly (0x2313), or shutdown
|
||||
cleanly (0x2413) before power is lost. They must be acted upon, as the
|
||||
wake up caused by the firmware will have negated most safety nets...
|
||||
|
||||
When any of the "too hot" alarms happen, according to Lenovo the user
|
||||
should suspend or hibernate the laptop (and in the case of battery
|
||||
alarms, unplug the AC adapter) to let it cool down. These alarms do
|
||||
signal that something is wrong, they should never happen on normal
|
||||
operating conditions.
|
||||
|
||||
The "extremely hot" alarms are emergencies. According to Lenovo, the
|
||||
operating system is to force either an immediate suspend or hibernate
|
||||
cycle, or a system shutdown. Obviously, something is very wrong if this
|
||||
happens.
|
||||
|
||||
Compatibility notes:
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef _ASM_X86_INTEL_SCU_IPC_H_
|
||||
#define _ASM_X86_INTEL_SCU_IPC_H_
|
||||
|
||||
/* Read single register */
|
||||
int intel_scu_ipc_ioread8(u16 addr, u8 *data);
|
||||
|
||||
/* Read two sequential registers */
|
||||
int intel_scu_ipc_ioread16(u16 addr, u16 *data);
|
||||
|
||||
/* Read four sequential registers */
|
||||
int intel_scu_ipc_ioread32(u16 addr, u32 *data);
|
||||
|
||||
/* Read a vector */
|
||||
int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
|
||||
|
||||
/* Write single register */
|
||||
int intel_scu_ipc_iowrite8(u16 addr, u8 data);
|
||||
|
||||
/* Write two sequential registers */
|
||||
int intel_scu_ipc_iowrite16(u16 addr, u16 data);
|
||||
|
||||
/* Write four sequential registers */
|
||||
int intel_scu_ipc_iowrite32(u16 addr, u32 data);
|
||||
|
||||
/* Write a vector */
|
||||
int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
|
||||
|
||||
/* Update single register based on the mask */
|
||||
int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask);
|
||||
|
||||
/*
|
||||
* Indirect register read
|
||||
* Can be used when SCCB(System Controller Configuration Block) register
|
||||
* HRIM(Honor Restricted IPC Messages) is set (bit 23)
|
||||
*/
|
||||
int intel_scu_ipc_register_read(u32 addr, u32 *data);
|
||||
|
||||
/*
|
||||
* Indirect register write
|
||||
* Can be used when SCCB(System Controller Configuration Block) register
|
||||
* HRIM(Honor Restricted IPC Messages) is set (bit 23)
|
||||
*/
|
||||
int intel_scu_ipc_register_write(u32 addr, u32 data);
|
||||
|
||||
/* Issue commands to the SCU with or without data */
|
||||
int intel_scu_ipc_simple_command(int cmd, int sub);
|
||||
int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
|
||||
u32 *out, int outlen);
|
||||
/* I2C control api */
|
||||
int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
|
||||
|
||||
/* Update FW version */
|
||||
int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
|
||||
|
||||
#endif
|
|
@ -390,6 +390,7 @@ config EEEPC_WMI
|
|||
depends on ACPI_WMI
|
||||
depends on INPUT
|
||||
depends on EXPERIMENTAL
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
select INPUT_SPARSEKMAP
|
||||
---help---
|
||||
Say Y here if you want to support WMI-based hotkeys on Eee PC laptops.
|
||||
|
@ -527,4 +528,13 @@ config ACPI_CMPC
|
|||
keys as input device, backlight device, tablet and accelerometer
|
||||
devices.
|
||||
|
||||
config INTEL_SCU_IPC
|
||||
bool "Intel SCU IPC Support"
|
||||
depends on X86_MRST
|
||||
default y
|
||||
---help---
|
||||
IPC is used to bridge the communications between kernel and SCU on
|
||||
some embedded Intel x86 platforms. This is not needed for PC-type
|
||||
machines.
|
||||
|
||||
endif # X86_PLATFORM_DEVICES
|
||||
|
|
|
@ -25,3 +25,4 @@ obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o
|
|||
obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
|
||||
obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
|
||||
obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
|
||||
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <acpi/acpi_drivers.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/rfkill.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
@ -37,7 +38,7 @@ struct cmpc_accel {
|
|||
|
||||
#define CMPC_ACCEL_HID "ACCE0000"
|
||||
#define CMPC_TABLET_HID "TBLT0000"
|
||||
#define CMPC_BL_HID "IPML200"
|
||||
#define CMPC_IPML_HID "IPML200"
|
||||
#define CMPC_KEYS_HID "FnBT0000"
|
||||
|
||||
/*
|
||||
|
@ -461,43 +462,168 @@ static const struct backlight_ops cmpc_bl_ops = {
|
|||
.update_status = cmpc_bl_update_status
|
||||
};
|
||||
|
||||
static int cmpc_bl_add(struct acpi_device *acpi)
|
||||
/*
|
||||
* RFKILL code.
|
||||
*/
|
||||
|
||||
static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle,
|
||||
unsigned long long *value)
|
||||
{
|
||||
struct backlight_properties props;
|
||||
union acpi_object param;
|
||||
struct acpi_object_list input;
|
||||
unsigned long long output;
|
||||
acpi_status status;
|
||||
|
||||
param.type = ACPI_TYPE_INTEGER;
|
||||
param.integer.value = 0xC1;
|
||||
input.count = 1;
|
||||
input.pointer = ¶m;
|
||||
status = acpi_evaluate_integer(handle, "GRDI", &input, &output);
|
||||
if (ACPI_SUCCESS(status))
|
||||
*value = output;
|
||||
return status;
|
||||
}
|
||||
|
||||
static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle,
|
||||
unsigned long long value)
|
||||
{
|
||||
union acpi_object param[2];
|
||||
struct acpi_object_list input;
|
||||
acpi_status status;
|
||||
unsigned long long output;
|
||||
|
||||
param[0].type = ACPI_TYPE_INTEGER;
|
||||
param[0].integer.value = 0xC1;
|
||||
param[1].type = ACPI_TYPE_INTEGER;
|
||||
param[1].integer.value = value;
|
||||
input.count = 2;
|
||||
input.pointer = param;
|
||||
status = acpi_evaluate_integer(handle, "GWRI", &input, &output);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void cmpc_rfkill_query(struct rfkill *rfkill, void *data)
|
||||
{
|
||||
acpi_status status;
|
||||
acpi_handle handle;
|
||||
unsigned long long state;
|
||||
bool blocked;
|
||||
|
||||
handle = data;
|
||||
status = cmpc_get_rfkill_wlan(handle, &state);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
blocked = state & 1 ? false : true;
|
||||
rfkill_set_sw_state(rfkill, blocked);
|
||||
}
|
||||
}
|
||||
|
||||
static int cmpc_rfkill_block(void *data, bool blocked)
|
||||
{
|
||||
acpi_status status;
|
||||
acpi_handle handle;
|
||||
unsigned long long state;
|
||||
|
||||
handle = data;
|
||||
status = cmpc_get_rfkill_wlan(handle, &state);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -ENODEV;
|
||||
if (blocked)
|
||||
state &= ~1;
|
||||
else
|
||||
state |= 1;
|
||||
status = cmpc_set_rfkill_wlan(handle, state);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -ENODEV;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rfkill_ops cmpc_rfkill_ops = {
|
||||
.query = cmpc_rfkill_query,
|
||||
.set_block = cmpc_rfkill_block,
|
||||
};
|
||||
|
||||
/*
|
||||
* Common backlight and rfkill code.
|
||||
*/
|
||||
|
||||
struct ipml200_dev {
|
||||
struct backlight_device *bd;
|
||||
struct rfkill *rf;
|
||||
};
|
||||
|
||||
static int cmpc_ipml_add(struct acpi_device *acpi)
|
||||
{
|
||||
int retval;
|
||||
struct ipml200_dev *ipml;
|
||||
struct backlight_properties props;
|
||||
|
||||
ipml = kmalloc(sizeof(*ipml), GFP_KERNEL);
|
||||
if (ipml == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(&props, 0, sizeof(struct backlight_properties));
|
||||
props.max_brightness = 7;
|
||||
bd = backlight_device_register("cmpc_bl", &acpi->dev, acpi->handle,
|
||||
&cmpc_bl_ops, &props);
|
||||
if (IS_ERR(bd))
|
||||
return PTR_ERR(bd);
|
||||
dev_set_drvdata(&acpi->dev, bd);
|
||||
ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev,
|
||||
acpi->handle, &cmpc_bl_ops,
|
||||
&props);
|
||||
if (IS_ERR(ipml->bd)) {
|
||||
retval = PTR_ERR(ipml->bd);
|
||||
goto out_bd;
|
||||
}
|
||||
|
||||
ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN,
|
||||
&cmpc_rfkill_ops, acpi->handle);
|
||||
/* rfkill_alloc may fail if RFKILL is disabled. We should still work
|
||||
* anyway. */
|
||||
if (!IS_ERR(ipml->rf)) {
|
||||
retval = rfkill_register(ipml->rf);
|
||||
if (retval) {
|
||||
rfkill_destroy(ipml->rf);
|
||||
ipml->rf = NULL;
|
||||
}
|
||||
} else {
|
||||
ipml->rf = NULL;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&acpi->dev, ipml);
|
||||
return 0;
|
||||
|
||||
out_bd:
|
||||
kfree(ipml);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int cmpc_bl_remove(struct acpi_device *acpi, int type)
|
||||
static int cmpc_ipml_remove(struct acpi_device *acpi, int type)
|
||||
{
|
||||
struct backlight_device *bd;
|
||||
struct ipml200_dev *ipml;
|
||||
|
||||
ipml = dev_get_drvdata(&acpi->dev);
|
||||
|
||||
backlight_device_unregister(ipml->bd);
|
||||
|
||||
if (ipml->rf) {
|
||||
rfkill_unregister(ipml->rf);
|
||||
rfkill_destroy(ipml->rf);
|
||||
}
|
||||
|
||||
kfree(ipml);
|
||||
|
||||
bd = dev_get_drvdata(&acpi->dev);
|
||||
backlight_device_unregister(bd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cmpc_bl_device_ids[] = {
|
||||
{CMPC_BL_HID, 0},
|
||||
static const struct acpi_device_id cmpc_ipml_device_ids[] = {
|
||||
{CMPC_IPML_HID, 0},
|
||||
{"", 0}
|
||||
};
|
||||
|
||||
static struct acpi_driver cmpc_bl_acpi_driver = {
|
||||
static struct acpi_driver cmpc_ipml_acpi_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "cmpc",
|
||||
.class = "cmpc",
|
||||
.ids = cmpc_bl_device_ids,
|
||||
.ids = cmpc_ipml_device_ids,
|
||||
.ops = {
|
||||
.add = cmpc_bl_add,
|
||||
.remove = cmpc_bl_remove
|
||||
.add = cmpc_ipml_add,
|
||||
.remove = cmpc_ipml_remove
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -580,7 +706,7 @@ static int cmpc_init(void)
|
|||
if (r)
|
||||
goto failed_keys;
|
||||
|
||||
r = acpi_bus_register_driver(&cmpc_bl_acpi_driver);
|
||||
r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver);
|
||||
if (r)
|
||||
goto failed_bl;
|
||||
|
||||
|
@ -598,7 +724,7 @@ failed_accel:
|
|||
acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
|
||||
|
||||
failed_tablet:
|
||||
acpi_bus_unregister_driver(&cmpc_bl_acpi_driver);
|
||||
acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
|
||||
|
||||
failed_bl:
|
||||
acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
|
||||
|
@ -611,7 +737,7 @@ static void cmpc_exit(void)
|
|||
{
|
||||
acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
|
||||
acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
|
||||
acpi_bus_unregister_driver(&cmpc_bl_acpi_driver);
|
||||
acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
|
||||
acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
|
||||
}
|
||||
|
||||
|
@ -621,7 +747,7 @@ module_exit(cmpc_exit);
|
|||
static const struct acpi_device_id cmpc_device_ids[] = {
|
||||
{CMPC_ACCEL_HID, 0},
|
||||
{CMPC_TABLET_HID, 0},
|
||||
{CMPC_BL_HID, 0},
|
||||
{CMPC_IPML_HID, 0},
|
||||
{CMPC_KEYS_HID, 0},
|
||||
{"", 0}
|
||||
};
|
||||
|
|
|
@ -206,7 +206,7 @@ static int eeepc_wmi_backlight_notify(struct eeepc_wmi *eeepc, int code)
|
|||
{
|
||||
struct backlight_device *bd = eeepc->backlight_device;
|
||||
int old = bd->props.brightness;
|
||||
int new;
|
||||
int new = old;
|
||||
|
||||
if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
|
||||
new = code - NOTIFY_BRNUP_MIN + 1;
|
||||
|
|
|
@ -1090,10 +1090,9 @@ static int __init fujitsu_init(void)
|
|||
if (acpi_disabled)
|
||||
return -ENODEV;
|
||||
|
||||
fujitsu = kmalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
|
||||
fujitsu = kzalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
|
||||
if (!fujitsu)
|
||||
return -ENOMEM;
|
||||
memset(fujitsu, 0, sizeof(struct fujitsu_t));
|
||||
fujitsu->keycode1 = KEY_PROG1;
|
||||
fujitsu->keycode2 = KEY_PROG2;
|
||||
fujitsu->keycode3 = KEY_PROG3;
|
||||
|
@ -1150,12 +1149,11 @@ static int __init fujitsu_init(void)
|
|||
|
||||
/* Register hotkey driver */
|
||||
|
||||
fujitsu_hotkey = kmalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
|
||||
fujitsu_hotkey = kzalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
|
||||
if (!fujitsu_hotkey) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_hotkey;
|
||||
}
|
||||
memset(fujitsu_hotkey, 0, sizeof(struct fujitsu_hotkey_t));
|
||||
|
||||
result = acpi_bus_register_driver(&acpi_fujitsu_hotkey_driver);
|
||||
if (result < 0) {
|
||||
|
|
|
@ -0,0 +1,829 @@
|
|||
/*
|
||||
* intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
|
||||
*
|
||||
* (C) Copyright 2008-2010 Intel Corporation
|
||||
* Author: Sreedhara DS (sreedhara.ds@intel.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2
|
||||
* of the License.
|
||||
*
|
||||
* SCU runing in ARC processor communicates with other entity running in IA
|
||||
* core through IPC mechanism which in turn messaging between IA core ad SCU.
|
||||
* SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
|
||||
* SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
|
||||
* IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
|
||||
* along with other APIs.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/intel_scu_ipc.h>
|
||||
|
||||
/* IPC defines the following message types */
|
||||
#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
|
||||
#define IPCMSG_BATTERY 0xEF /* Coulomb Counter Accumulator */
|
||||
#define IPCMSG_FW_UPDATE 0xFE /* Firmware update */
|
||||
#define IPCMSG_PCNTRL 0xFF /* Power controller unit read/write */
|
||||
#define IPCMSG_FW_REVISION 0xF4 /* Get firmware revision */
|
||||
|
||||
/* Command id associated with message IPCMSG_PCNTRL */
|
||||
#define IPC_CMD_PCNTRL_W 0 /* Register write */
|
||||
#define IPC_CMD_PCNTRL_R 1 /* Register read */
|
||||
#define IPC_CMD_PCNTRL_M 2 /* Register read-modify-write */
|
||||
|
||||
/* Miscelaneous Command ids */
|
||||
#define IPC_CMD_INDIRECT_RD 2 /* 32bit indirect read */
|
||||
#define IPC_CMD_INDIRECT_WR 5 /* 32bit indirect write */
|
||||
|
||||
/*
|
||||
* IPC register summary
|
||||
*
|
||||
* IPC register blocks are memory mapped at fixed address of 0xFF11C000
|
||||
* To read or write information to the SCU, driver writes to IPC-1 memory
|
||||
* mapped registers (base address 0xFF11C000). The following is the IPC
|
||||
* mechanism
|
||||
*
|
||||
* 1. IA core cDMI interface claims this transaction and converts it to a
|
||||
* Transaction Layer Packet (TLP) message which is sent across the cDMI.
|
||||
*
|
||||
* 2. South Complex cDMI block receives this message and writes it to
|
||||
* the IPC-1 register block, causing an interrupt to the SCU
|
||||
*
|
||||
* 3. SCU firmware decodes this interrupt and IPC message and the appropriate
|
||||
* message handler is called within firmware.
|
||||
*/
|
||||
|
||||
#define IPC_BASE_ADDR 0xFF11C000 /* IPC1 base register address */
|
||||
#define IPC_MAX_ADDR 0x100 /* Maximum IPC regisers */
|
||||
#define IPC_WWBUF_SIZE 16 /* IPC Write buffer Size */
|
||||
#define IPC_RWBUF_SIZE 16 /* IPC Read buffer Size */
|
||||
#define IPC_I2C_BASE 0xFF12B000 /* I2C control register base address */
|
||||
#define IPC_I2C_MAX_ADDR 0x10 /* Maximum I2C regisers */
|
||||
|
||||
static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
|
||||
static void ipc_remove(struct pci_dev *pdev);
|
||||
|
||||
struct intel_scu_ipc_dev {
|
||||
struct pci_dev *pdev;
|
||||
void __iomem *ipc_base;
|
||||
void __iomem *i2c_base;
|
||||
};
|
||||
|
||||
static struct intel_scu_ipc_dev ipcdev; /* Only one for now */
|
||||
|
||||
static int platform = 1;
|
||||
module_param(platform, int, 0);
|
||||
MODULE_PARM_DESC(platform, "1 for moorestown platform");
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* IPC Read Buffer (Read Only):
|
||||
* 16 byte buffer for receiving data from SCU, if IPC command
|
||||
* processing results in response data
|
||||
*/
|
||||
#define IPC_READ_BUFFER 0x90
|
||||
|
||||
#define IPC_I2C_CNTRL_ADDR 0
|
||||
#define I2C_DATA_ADDR 0x04
|
||||
|
||||
static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
|
||||
|
||||
/*
|
||||
* Command Register (Write Only):
|
||||
* A write to this register results in an interrupt to the SCU core processor
|
||||
* Format:
|
||||
* |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
|
||||
*/
|
||||
static inline void ipc_command(u32 cmd) /* Send ipc command */
|
||||
{
|
||||
writel(cmd, ipcdev.ipc_base);
|
||||
}
|
||||
|
||||
/*
|
||||
* IPC Write Buffer (Write Only):
|
||||
* 16-byte buffer for sending data associated with IPC command to
|
||||
* SCU. Size of the data is specified in the IPC_COMMAND_REG register
|
||||
*/
|
||||
static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */
|
||||
{
|
||||
writel(data, ipcdev.ipc_base + 0x80 + offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* IPC destination Pointer (Write Only):
|
||||
* Use content as pointer for destination write
|
||||
*/
|
||||
static inline void ipc_write_dptr(u32 data) /* Write dptr data */
|
||||
{
|
||||
writel(data, ipcdev.ipc_base + 0x0C);
|
||||
}
|
||||
|
||||
/*
|
||||
* IPC Source Pointer (Write Only):
|
||||
* Use content as pointer for read location
|
||||
*/
|
||||
static inline void ipc_write_sptr(u32 data) /* Write dptr data */
|
||||
{
|
||||
writel(data, ipcdev.ipc_base + 0x08);
|
||||
}
|
||||
|
||||
/*
|
||||
* Status Register (Read Only):
|
||||
* Driver will read this register to get the ready/busy status of the IPC
|
||||
* block and error status of the IPC command that was just processed by SCU
|
||||
* Format:
|
||||
* |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
|
||||
*/
|
||||
|
||||
static inline u8 ipc_read_status(void)
|
||||
{
|
||||
return __raw_readl(ipcdev.ipc_base + 0x04);
|
||||
}
|
||||
|
||||
static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */
|
||||
{
|
||||
return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
|
||||
}
|
||||
|
||||
static inline u8 ipc_data_readl(u32 offset) /* Read ipc u32 data */
|
||||
{
|
||||
return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
|
||||
}
|
||||
|
||||
static inline int busy_loop(void) /* Wait till scu status is busy */
|
||||
{
|
||||
u32 status = 0;
|
||||
u32 loop_count = 0;
|
||||
|
||||
status = ipc_read_status();
|
||||
while (status & 1) {
|
||||
udelay(1); /* scu processing time is in few u secods */
|
||||
status = ipc_read_status();
|
||||
loop_count++;
|
||||
/* break if scu doesn't reset busy bit after huge retry */
|
||||
if (loop_count > 100000) {
|
||||
dev_err(&ipcdev.pdev->dev, "IPC timed out");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
return (status >> 1) & 1;
|
||||
}
|
||||
|
||||
/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
|
||||
static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
|
||||
{
|
||||
int nc;
|
||||
u32 offset = 0;
|
||||
u32 err = 0;
|
||||
u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
|
||||
u32 *wbuf = (u32 *)&cbuf;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev.pdev == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (platform == 1) {
|
||||
/* Entry is 4 bytes for read/write, 5 bytes for read modify */
|
||||
for (nc = 0; nc < count; nc++) {
|
||||
cbuf[offset] = addr[nc];
|
||||
cbuf[offset + 1] = addr[nc] >> 8;
|
||||
if (id != IPC_CMD_PCNTRL_R)
|
||||
cbuf[offset + 2] = data[nc];
|
||||
if (id == IPC_CMD_PCNTRL_M) {
|
||||
cbuf[offset + 3] = data[nc + 1];
|
||||
offset += 1;
|
||||
}
|
||||
offset += 3;
|
||||
}
|
||||
for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
|
||||
ipc_data_writel(wbuf[nc], offset); /* Write wbuff */
|
||||
|
||||
} else {
|
||||
for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
|
||||
ipc_data_writel(addr[nc], offset); /* Write addresses */
|
||||
if (id != IPC_CMD_PCNTRL_R) {
|
||||
for (nc = 0; nc < count; nc++, offset++)
|
||||
ipc_data_writel(data[nc], offset); /* Write data */
|
||||
if (id == IPC_CMD_PCNTRL_M)
|
||||
ipc_data_writel(data[nc + 1], offset); /* Mask value*/
|
||||
}
|
||||
}
|
||||
|
||||
if (id != IPC_CMD_PCNTRL_M)
|
||||
ipc_command((count * 3) << 16 | id << 12 | 0 << 8 | op);
|
||||
else
|
||||
ipc_command((count * 4) << 16 | id << 12 | 0 << 8 | op);
|
||||
|
||||
err = busy_loop();
|
||||
|
||||
if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
|
||||
/* Workaround: values are read as 0 without memcpy_fromio */
|
||||
memcpy_fromio(cbuf, ipcdev.ipc_base + IPC_READ_BUFFER, 16);
|
||||
if (platform == 1) {
|
||||
for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
|
||||
data[nc] = ipc_data_readb(offset);
|
||||
} else {
|
||||
for (nc = 0; nc < count; nc++)
|
||||
data[nc] = ipc_data_readb(nc);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_ioread8 - read a word via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: return pointer for read byte
|
||||
*
|
||||
* Read a single register. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_ioread8(u16 addr, u8 *data)
|
||||
{
|
||||
return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_ioread8);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_ioread16 - read a word via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: return pointer for read word
|
||||
*
|
||||
* Read a register pair. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_ioread16(u16 addr, u16 *data)
|
||||
{
|
||||
u16 x[2] = {addr, addr + 1 };
|
||||
return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_ioread16);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_ioread32 - read a dword via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: return pointer for read dword
|
||||
*
|
||||
* Read four registers. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_ioread32(u16 addr, u32 *data)
|
||||
{
|
||||
u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
|
||||
return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_ioread32);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_iowrite8 - write a byte via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: byte to write
|
||||
*
|
||||
* Write a single register. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_iowrite8(u16 addr, u8 data)
|
||||
{
|
||||
return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_iowrite16 - write a word via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: word to write
|
||||
*
|
||||
* Write two registers. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_iowrite16(u16 addr, u16 data)
|
||||
{
|
||||
u16 x[2] = {addr, addr + 1 };
|
||||
return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_iowrite32 - write a dword via the SCU
|
||||
* @addr: register on SCU
|
||||
* @data: dword to write
|
||||
*
|
||||
* Write four registers. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_iowrite32(u16 addr, u32 data)
|
||||
{
|
||||
u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
|
||||
return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_readvv - read a set of registers
|
||||
* @addr: register list
|
||||
* @data: bytes to return
|
||||
* @len: length of array
|
||||
*
|
||||
* Read registers. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* The largest array length permitted by the hardware is 5 items.
|
||||
*
|
||||
* This function may sleep.
|
||||
*/
|
||||
int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
|
||||
{
|
||||
return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_readv);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_writev - write a set of registers
|
||||
* @addr: register list
|
||||
* @data: bytes to write
|
||||
* @len: length of array
|
||||
*
|
||||
* Write registers. Returns 0 on success or an error code. All
|
||||
* locking between SCU accesses is handled for the caller.
|
||||
*
|
||||
* The largest array length permitted by the hardware is 5 items.
|
||||
*
|
||||
* This function may sleep.
|
||||
*
|
||||
*/
|
||||
int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
|
||||
{
|
||||
return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_writev);
|
||||
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_update_register - r/m/w a register
|
||||
* @addr: register address
|
||||
* @bits: bits to update
|
||||
* @mask: mask of bits to update
|
||||
*
|
||||
* Read-modify-write power control unit register. The first data argument
|
||||
* must be register value and second is mask value
|
||||
* mask is a bitmap that indicates which bits to update.
|
||||
* 0 = masked. Don't modify this bit, 1 = modify this bit.
|
||||
* returns 0 on success or an error code.
|
||||
*
|
||||
* This function may sleep. Locking between SCU accesses is handled
|
||||
* for the caller.
|
||||
*/
|
||||
int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask)
|
||||
{
|
||||
u8 data[2] = { bits, mask };
|
||||
return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_update_register);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_register_read - 32bit indirect read
|
||||
* @addr: register address
|
||||
* @value: 32bit value return
|
||||
*
|
||||
* Performs IA 32 bit indirect read, returns 0 on success, or an
|
||||
* error code.
|
||||
*
|
||||
* Can be used when SCCB(System Controller Configuration Block) register
|
||||
* HRIM(Honor Restricted IPC Messages) is set (bit 23)
|
||||
*
|
||||
* This function may sleep. Locking for SCU accesses is handled for
|
||||
* the caller.
|
||||
*/
|
||||
int intel_scu_ipc_register_read(u32 addr, u32 *value)
|
||||
{
|
||||
u32 err = 0;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev.pdev == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENODEV;
|
||||
}
|
||||
ipc_write_sptr(addr);
|
||||
ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
|
||||
err = busy_loop();
|
||||
*value = ipc_data_readl(0);
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_register_read);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_register_write - 32bit indirect write
|
||||
* @addr: register address
|
||||
* @value: 32bit value to write
|
||||
*
|
||||
* Performs IA 32 bit indirect write, returns 0 on success, or an
|
||||
* error code.
|
||||
*
|
||||
* Can be used when SCCB(System Controller Configuration Block) register
|
||||
* HRIM(Honor Restricted IPC Messages) is set (bit 23)
|
||||
*
|
||||
* This function may sleep. Locking for SCU accesses is handled for
|
||||
* the caller.
|
||||
*/
|
||||
int intel_scu_ipc_register_write(u32 addr, u32 value)
|
||||
{
|
||||
u32 err = 0;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev.pdev == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENODEV;
|
||||
}
|
||||
ipc_write_dptr(addr);
|
||||
ipc_data_writel(value, 0);
|
||||
ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
|
||||
err = busy_loop();
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_register_write);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_simple_command - send a simple command
|
||||
* @cmd: command
|
||||
* @sub: sub type
|
||||
*
|
||||
* Issue a simple command to the SCU. Do not use this interface if
|
||||
* you must then access data as any data values may be overwritten
|
||||
* by another SCU access by the time this function returns.
|
||||
*
|
||||
* This function may sleep. Locking for SCU accesses is handled for
|
||||
* the caller.
|
||||
*/
|
||||
int intel_scu_ipc_simple_command(int cmd, int sub)
|
||||
{
|
||||
u32 err = 0;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev.pdev == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENODEV;
|
||||
}
|
||||
ipc_command(cmd << 12 | sub);
|
||||
err = busy_loop();
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_simple_command);
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_command - command with data
|
||||
* @cmd: command
|
||||
* @sub: sub type
|
||||
* @in: input data
|
||||
* @inlen: input length
|
||||
* @out: output data
|
||||
* @outlein: output length
|
||||
*
|
||||
* Issue a command to the SCU which involves data transfers. Do the
|
||||
* data copies under the lock but leave it for the caller to interpret
|
||||
*/
|
||||
|
||||
int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
|
||||
u32 *out, int outlen)
|
||||
{
|
||||
u32 err = 0;
|
||||
int i = 0;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
if (ipcdev.pdev == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for (i = 0; i < inlen; i++)
|
||||
ipc_data_writel(*in++, 4 * i);
|
||||
|
||||
ipc_command(cmd << 12 | sub);
|
||||
err = busy_loop();
|
||||
|
||||
for (i = 0; i < outlen; i++)
|
||||
*out++ = ipc_data_readl(4 * i);
|
||||
|
||||
mutex_unlock(&ipclock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_command);
|
||||
|
||||
/*I2C commands */
|
||||
#define IPC_I2C_WRITE 1 /* I2C Write command */
|
||||
#define IPC_I2C_READ 2 /* I2C Read command */
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_i2c_cntrl - I2C read/write operations
|
||||
* @addr: I2C address + command bits
|
||||
* @data: data to read/write
|
||||
*
|
||||
* Perform an an I2C read/write operation via the SCU. All locking is
|
||||
* handled for the caller. This function may sleep.
|
||||
*
|
||||
* Returns an error code or 0 on success.
|
||||
*
|
||||
* This has to be in the IPC driver for the locking.
|
||||
*/
|
||||
int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
|
||||
{
|
||||
u32 cmd = 0;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
cmd = (addr >> 24) & 0xFF;
|
||||
if (cmd == IPC_I2C_READ) {
|
||||
writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
|
||||
/* Write not getting updated without delay */
|
||||
mdelay(1);
|
||||
*data = readl(ipcdev.i2c_base + I2C_DATA_ADDR);
|
||||
} else if (cmd == IPC_I2C_WRITE) {
|
||||
writel(addr, ipcdev.i2c_base + I2C_DATA_ADDR);
|
||||
mdelay(1);
|
||||
writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
|
||||
} else {
|
||||
dev_err(&ipcdev.pdev->dev,
|
||||
"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
|
||||
|
||||
mutex_unlock(&ipclock);
|
||||
return -1;
|
||||
}
|
||||
mutex_unlock(&ipclock);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
|
||||
|
||||
#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
|
||||
#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
|
||||
#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
|
||||
#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
|
||||
/* IPC inform SCU to get ready for update process */
|
||||
#define IPC_CMD_FW_UPDATE_READY 0x10FE
|
||||
/* IPC inform SCU to go for update process */
|
||||
#define IPC_CMD_FW_UPDATE_GO 0x20FE
|
||||
/* Status code for fw update */
|
||||
#define IPC_FW_UPDATE_SUCCESS 0x444f4e45 /* Status code 'DONE' */
|
||||
#define IPC_FW_UPDATE_BADN 0x4241444E /* Status code 'BADN' */
|
||||
#define IPC_FW_TXHIGH 0x54784849 /* Status code 'IPC_FW_TXHIGH' */
|
||||
#define IPC_FW_TXLOW 0x54784c4f /* Status code 'IPC_FW_TXLOW' */
|
||||
|
||||
struct fw_update_mailbox {
|
||||
u32 status;
|
||||
u32 scu_flag;
|
||||
u32 driver_flag;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* intel_scu_ipc_fw_update - Firmware update utility
|
||||
* @buffer: firmware buffer
|
||||
* @length: size of firmware buffer
|
||||
*
|
||||
* This function provides an interface to load the firmware into
|
||||
* the SCU. Returns 0 on success or -1 on failure
|
||||
*/
|
||||
int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
|
||||
{
|
||||
void __iomem *fw_update_base;
|
||||
struct fw_update_mailbox __iomem *mailbox = NULL;
|
||||
int retry_cnt = 0;
|
||||
u32 status;
|
||||
|
||||
mutex_lock(&ipclock);
|
||||
fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
|
||||
if (fw_update_base == NULL) {
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
mailbox = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
|
||||
sizeof(struct fw_update_mailbox));
|
||||
if (mailbox == NULL) {
|
||||
iounmap(fw_update_base);
|
||||
mutex_unlock(&ipclock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ipc_command(IPC_CMD_FW_UPDATE_READY);
|
||||
|
||||
/* Intitialize mailbox */
|
||||
writel(0, &mailbox->status);
|
||||
writel(0, &mailbox->scu_flag);
|
||||
writel(0, &mailbox->driver_flag);
|
||||
|
||||
/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
|
||||
memcpy_toio(fw_update_base, buffer, 0x800);
|
||||
|
||||
/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
|
||||
* Upon receiving this command, SCU will write the 2K MIP header
|
||||
* from 0xFFFC0000 into NAND.
|
||||
* SCU will write a status code into the Mailbox, and then set scu_flag.
|
||||
*/
|
||||
|
||||
ipc_command(IPC_CMD_FW_UPDATE_GO);
|
||||
|
||||
/*Driver stalls until scu_flag is set */
|
||||
while (readl(&mailbox->scu_flag) != 1) {
|
||||
rmb();
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
/* Driver checks Mailbox status.
|
||||
* If the status is 'BADN', then abort (bad NAND).
|
||||
* If the status is 'IPC_FW_TXLOW', then continue.
|
||||
*/
|
||||
while (readl(&mailbox->status) != IPC_FW_TXLOW) {
|
||||
rmb();
|
||||
mdelay(10);
|
||||
}
|
||||
mdelay(10);
|
||||
|
||||
update_retry:
|
||||
if (retry_cnt > 5)
|
||||
goto update_end;
|
||||
|
||||
if (readl(&mailbox->status) != IPC_FW_TXLOW)
|
||||
goto update_end;
|
||||
buffer = buffer + 0x800;
|
||||
memcpy_toio(fw_update_base, buffer, 0x20000);
|
||||
writel(1, &mailbox->driver_flag);
|
||||
while (readl(&mailbox->scu_flag) == 1) {
|
||||
rmb();
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
/* check for 'BADN' */
|
||||
if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
|
||||
goto update_end;
|
||||
|
||||
while (readl(&mailbox->status) != IPC_FW_TXHIGH) {
|
||||
rmb();
|
||||
mdelay(10);
|
||||
}
|
||||
mdelay(10);
|
||||
|
||||
if (readl(&mailbox->status) != IPC_FW_TXHIGH)
|
||||
goto update_end;
|
||||
|
||||
buffer = buffer + 0x20000;
|
||||
memcpy_toio(fw_update_base, buffer, 0x20000);
|
||||
writel(0, &mailbox->driver_flag);
|
||||
|
||||
while (mailbox->scu_flag == 0) {
|
||||
rmb();
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
/* check for 'BADN' */
|
||||
if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
|
||||
goto update_end;
|
||||
|
||||
if (readl(&mailbox->status) == IPC_FW_TXLOW) {
|
||||
++retry_cnt;
|
||||
goto update_retry;
|
||||
}
|
||||
|
||||
update_end:
|
||||
status = readl(&mailbox->status);
|
||||
|
||||
iounmap(fw_update_base);
|
||||
iounmap(mailbox);
|
||||
mutex_unlock(&ipclock);
|
||||
|
||||
if (status == IPC_FW_UPDATE_SUCCESS)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(intel_scu_ipc_fw_update);
|
||||
|
||||
/*
|
||||
* Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
|
||||
* When ioc bit is set to 1, caller api must wait for interrupt handler called
|
||||
* which in turn unlocks the caller api. Currently this is not used
|
||||
*
|
||||
* This is edge triggered so we need take no action to clear anything
|
||||
*/
|
||||
static irqreturn_t ioc(int irq, void *dev_id)
|
||||
{
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipc_probe - probe an Intel SCU IPC
|
||||
* @dev: the PCI device matching
|
||||
* @id: entry in the match table
|
||||
*
|
||||
* Enable and install an intel SCU IPC. This appears in the PCI space
|
||||
* but uses some hard coded addresses as well.
|
||||
*/
|
||||
static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
int err;
|
||||
resource_size_t pci_resource;
|
||||
|
||||
if (ipcdev.pdev) /* We support only one SCU */
|
||||
return -EBUSY;
|
||||
|
||||
ipcdev.pdev = pci_dev_get(dev);
|
||||
|
||||
err = pci_enable_device(dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = pci_request_regions(dev, "intel_scu_ipc");
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pci_resource = pci_resource_start(dev, 0);
|
||||
if (!pci_resource)
|
||||
return -ENOMEM;
|
||||
|
||||
if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev))
|
||||
return -EBUSY;
|
||||
|
||||
ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
|
||||
if (!ipcdev.ipc_base)
|
||||
return -ENOMEM;
|
||||
|
||||
ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
|
||||
if (!ipcdev.i2c_base) {
|
||||
iounmap(ipcdev.ipc_base);
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ipc_remove - remove a bound IPC device
|
||||
* @pdev: PCI device
|
||||
*
|
||||
* In practice the SCU is not removable but this function is also
|
||||
* called for each device on a module unload or cleanup which is the
|
||||
* path that will get used.
|
||||
*
|
||||
* Free up the mappings and release the PCI resources
|
||||
*/
|
||||
static void ipc_remove(struct pci_dev *pdev)
|
||||
{
|
||||
free_irq(pdev->irq, &ipcdev);
|
||||
pci_release_regions(pdev);
|
||||
pci_dev_put(ipcdev.pdev);
|
||||
iounmap(ipcdev.ipc_base);
|
||||
iounmap(ipcdev.i2c_base);
|
||||
ipcdev.pdev = NULL;
|
||||
}
|
||||
|
||||
static const struct pci_device_id pci_ids[] = {
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
|
||||
{ 0,}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pci_ids);
|
||||
|
||||
static struct pci_driver ipc_driver = {
|
||||
.name = "intel_scu_ipc",
|
||||
.id_table = pci_ids,
|
||||
.probe = ipc_probe,
|
||||
.remove = ipc_remove,
|
||||
};
|
||||
|
||||
|
||||
static int __init intel_scu_ipc_init(void)
|
||||
{
|
||||
return pci_register_driver(&ipc_driver);
|
||||
}
|
||||
|
||||
static void __exit intel_scu_ipc_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&ipc_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
|
||||
MODULE_DESCRIPTION("Intel SCU IPC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(intel_scu_ipc_init);
|
||||
module_exit(intel_scu_ipc_exit);
|
|
@ -59,6 +59,7 @@
|
|||
#include <linux/backlight.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/i8042.h>
|
||||
|
||||
#define MSI_DRIVER_VERSION "0.5"
|
||||
|
||||
|
@ -118,7 +119,8 @@ static int set_lcd_level(int level)
|
|||
buf[0] = 0x80;
|
||||
buf[1] = (u8) (level*31);
|
||||
|
||||
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1);
|
||||
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
|
||||
NULL, 0, 1);
|
||||
}
|
||||
|
||||
static int get_lcd_level(void)
|
||||
|
@ -126,7 +128,8 @@ static int get_lcd_level(void)
|
|||
u8 wdata = 0, rdata;
|
||||
int result;
|
||||
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
|
||||
&rdata, 1, 1);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
|
@ -138,7 +141,8 @@ static int get_auto_brightness(void)
|
|||
u8 wdata = 4, rdata;
|
||||
int result;
|
||||
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
|
||||
&rdata, 1, 1);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
|
@ -152,14 +156,16 @@ static int set_auto_brightness(int enable)
|
|||
|
||||
wdata[0] = 4;
|
||||
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1);
|
||||
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1,
|
||||
&rdata, 1, 1);
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
wdata[0] = 0x84;
|
||||
wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
|
||||
|
||||
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1);
|
||||
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2,
|
||||
NULL, 0, 1);
|
||||
}
|
||||
|
||||
static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
|
||||
|
@ -254,7 +260,7 @@ static int bl_update_status(struct backlight_device *b)
|
|||
return set_lcd_level(b->props.brightness);
|
||||
}
|
||||
|
||||
static struct backlight_ops msibl_ops = {
|
||||
static const struct backlight_ops msibl_ops = {
|
||||
.get_brightness = bl_get_brightness,
|
||||
.update_status = bl_update_status,
|
||||
};
|
||||
|
@ -353,7 +359,8 @@ static ssize_t store_lcd_level(struct device *dev,
|
|||
|
||||
int level, ret;
|
||||
|
||||
if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX))
|
||||
if (sscanf(buf, "%i", &level) != 1 ||
|
||||
(level < 0 || level >= MSI_LCD_LEVEL_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
ret = set_lcd_level(level);
|
||||
|
@ -393,7 +400,8 @@ static ssize_t store_auto_brightness(struct device *dev,
|
|||
}
|
||||
|
||||
static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
|
||||
static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness);
|
||||
static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
|
||||
store_auto_brightness);
|
||||
static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
|
||||
static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
|
||||
static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
|
||||
|
@ -424,7 +432,8 @@ static struct platform_device *msipf_device;
|
|||
|
||||
static int dmi_check_cb(const struct dmi_system_id *id)
|
||||
{
|
||||
printk("msi-laptop: Identified laptop model '%s'.\n", id->ident);
|
||||
printk(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n",
|
||||
id->ident);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -435,7 +444,8 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
|
||||
DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD")
|
||||
DMI_MATCH(DMI_CHASSIS_VENDOR,
|
||||
"MICRO-STAR INT'L CO.,LTD")
|
||||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
|
@ -465,7 +475,8 @@ static struct dmi_system_id __initdata msi_dmi_table[] = {
|
|||
DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
|
||||
DMI_MATCH(DMI_CHASSIS_VENDOR, "MICRO-STAR INT'L CO.,LTD")
|
||||
DMI_MATCH(DMI_CHASSIS_VENDOR,
|
||||
"MICRO-STAR INT'L CO.,LTD")
|
||||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
|
@ -484,6 +495,35 @@ static struct dmi_system_id __initdata msi_load_scm_models_dmi_table[] = {
|
|||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
{
|
||||
.ident = "MSI N051",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR,
|
||||
"MICRO-STAR INTERNATIONAL CO., LTD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"),
|
||||
DMI_MATCH(DMI_CHASSIS_VENDOR,
|
||||
"MICRO-STAR INTERNATIONAL CO., LTD")
|
||||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
{
|
||||
.ident = "MSI N014",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR,
|
||||
"MICRO-STAR INTERNATIONAL CO., LTD"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
|
||||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
{
|
||||
.ident = "MSI CR620",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR,
|
||||
"Micro-Star International"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
|
||||
},
|
||||
.callback = dmi_check_cb
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -552,11 +592,71 @@ static void rfkill_cleanup(void)
|
|||
}
|
||||
}
|
||||
|
||||
static void msi_update_rfkill(struct work_struct *ignored)
|
||||
{
|
||||
get_wireless_state_ec_standard();
|
||||
|
||||
if (rfk_wlan)
|
||||
rfkill_set_sw_state(rfk_wlan, !wlan_s);
|
||||
if (rfk_bluetooth)
|
||||
rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
|
||||
if (rfk_threeg)
|
||||
rfkill_set_sw_state(rfk_threeg, !threeg_s);
|
||||
}
|
||||
static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill);
|
||||
|
||||
static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
|
||||
struct serio *port)
|
||||
{
|
||||
static bool extended;
|
||||
|
||||
if (str & 0x20)
|
||||
return false;
|
||||
|
||||
/* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/
|
||||
if (unlikely(data == 0xe0)) {
|
||||
extended = true;
|
||||
return false;
|
||||
} else if (unlikely(extended)) {
|
||||
switch (data) {
|
||||
case 0x54:
|
||||
case 0x62:
|
||||
case 0x76:
|
||||
schedule_delayed_work(&msi_rfkill_work,
|
||||
round_jiffies_relative(0.5 * HZ));
|
||||
break;
|
||||
}
|
||||
extended = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void msi_init_rfkill(struct work_struct *ignored)
|
||||
{
|
||||
if (rfk_wlan) {
|
||||
rfkill_set_sw_state(rfk_wlan, !wlan_s);
|
||||
rfkill_wlan_set(NULL, !wlan_s);
|
||||
}
|
||||
if (rfk_bluetooth) {
|
||||
rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
|
||||
rfkill_bluetooth_set(NULL, !bluetooth_s);
|
||||
}
|
||||
if (rfk_threeg) {
|
||||
rfkill_set_sw_state(rfk_threeg, !threeg_s);
|
||||
rfkill_threeg_set(NULL, !threeg_s);
|
||||
}
|
||||
}
|
||||
static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
|
||||
|
||||
static int rfkill_init(struct platform_device *sdev)
|
||||
{
|
||||
/* add rfkill */
|
||||
int retval;
|
||||
|
||||
/* keep the hardware wireless state */
|
||||
get_wireless_state_ec_standard();
|
||||
|
||||
rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
|
||||
RFKILL_TYPE_BLUETOOTH,
|
||||
&rfkill_bluetooth_ops, NULL);
|
||||
|
@ -590,6 +690,10 @@ static int rfkill_init(struct platform_device *sdev)
|
|||
goto err_threeg;
|
||||
}
|
||||
|
||||
/* schedule to run rfkill state initial */
|
||||
schedule_delayed_work(&msi_rfkill_init,
|
||||
round_jiffies_relative(1 * HZ));
|
||||
|
||||
return 0;
|
||||
|
||||
err_threeg:
|
||||
|
@ -653,9 +757,24 @@ static int load_scm_model_init(struct platform_device *sdev)
|
|||
/* initial rfkill */
|
||||
result = rfkill_init(sdev);
|
||||
if (result < 0)
|
||||
return result;
|
||||
goto fail_rfkill;
|
||||
|
||||
result = i8042_install_filter(msi_laptop_i8042_filter);
|
||||
if (result) {
|
||||
printk(KERN_ERR
|
||||
"msi-laptop: Unable to install key filter\n");
|
||||
goto fail_filter;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_filter:
|
||||
rfkill_cleanup();
|
||||
|
||||
fail_rfkill:
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static int __init msi_init(void)
|
||||
|
@ -714,7 +833,8 @@ static int __init msi_init(void)
|
|||
goto fail_platform_device1;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
||||
ret = sysfs_create_group(&msipf_device->dev.kobj,
|
||||
&msipf_attribute_group);
|
||||
if (ret)
|
||||
goto fail_platform_device2;
|
||||
|
||||
|
@ -739,6 +859,11 @@ static int __init msi_init(void)
|
|||
|
||||
fail_platform_device2:
|
||||
|
||||
if (load_scm_model) {
|
||||
i8042_remove_filter(msi_laptop_i8042_filter);
|
||||
cancel_delayed_work_sync(&msi_rfkill_work);
|
||||
rfkill_cleanup();
|
||||
}
|
||||
platform_device_del(msipf_device);
|
||||
|
||||
fail_platform_device1:
|
||||
|
@ -758,6 +883,11 @@ fail_backlight:
|
|||
|
||||
static void __exit msi_cleanup(void)
|
||||
{
|
||||
if (load_scm_model) {
|
||||
i8042_remove_filter(msi_laptop_i8042_filter);
|
||||
cancel_delayed_work_sync(&msi_rfkill_work);
|
||||
rfkill_cleanup();
|
||||
}
|
||||
|
||||
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
||||
if (!old_ec_model && threeg_exists)
|
||||
|
@ -766,8 +896,6 @@ static void __exit msi_cleanup(void)
|
|||
platform_driver_unregister(&msipf_driver);
|
||||
backlight_device_unregister(msibl_device);
|
||||
|
||||
rfkill_cleanup();
|
||||
|
||||
/* Enable automatic brightness control again */
|
||||
if (auto_brightness != 2)
|
||||
set_auto_brightness(1);
|
||||
|
@ -788,3 +916,6 @@ MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-105
|
|||
MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
|
||||
MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
|
||||
MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
|
||||
MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
|
||||
MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
|
||||
MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
|
||||
|
|
|
@ -122,8 +122,14 @@ enum {
|
|||
TP_NVRAM_POS_LEVEL_VOLUME = 0,
|
||||
};
|
||||
|
||||
/* Misc NVRAM-related */
|
||||
enum {
|
||||
TP_NVRAM_LEVEL_VOLUME_MAX = 14,
|
||||
};
|
||||
|
||||
/* ACPI HIDs */
|
||||
#define TPACPI_ACPI_HKEY_HID "IBM0068"
|
||||
#define TPACPI_ACPI_EC_HID "PNP0C09"
|
||||
|
||||
/* Input IDs */
|
||||
#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
|
||||
|
@ -299,8 +305,8 @@ static struct {
|
|||
u32 hotkey_tablet:1;
|
||||
u32 light:1;
|
||||
u32 light_status:1;
|
||||
u32 bright_16levels:1;
|
||||
u32 bright_acpimode:1;
|
||||
u32 bright_unkfw:1;
|
||||
u32 wan:1;
|
||||
u32 uwb:1;
|
||||
u32 fan_ctrl_status_undef:1;
|
||||
|
@ -363,6 +369,9 @@ struct tpacpi_led_classdev {
|
|||
unsigned int led;
|
||||
};
|
||||
|
||||
/* brightness level capabilities */
|
||||
static unsigned int bright_maxlvl; /* 0 = unknown */
|
||||
|
||||
#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
|
||||
static int dbg_wlswemul;
|
||||
static int tpacpi_wlsw_emulstate;
|
||||
|
@ -480,6 +489,15 @@ static unsigned long __init tpacpi_check_quirks(
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline bool __pure __init tpacpi_is_lenovo(void)
|
||||
{
|
||||
return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
|
||||
}
|
||||
|
||||
static inline bool __pure __init tpacpi_is_ibm(void)
|
||||
{
|
||||
return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
****************************************************************************
|
||||
|
@ -494,21 +512,13 @@ static unsigned long __init tpacpi_check_quirks(
|
|||
*/
|
||||
|
||||
static acpi_handle root_handle;
|
||||
static acpi_handle ec_handle;
|
||||
|
||||
#define TPACPI_HANDLE(object, parent, paths...) \
|
||||
static acpi_handle object##_handle; \
|
||||
static acpi_handle *object##_parent = &parent##_handle; \
|
||||
static char *object##_path; \
|
||||
static char *object##_paths[] = { paths }
|
||||
|
||||
TPACPI_HANDLE(ec, root, "\\_SB.PCI0.ISA.EC0", /* 240, 240x */
|
||||
"\\_SB.PCI.ISA.EC", /* 570 */
|
||||
"\\_SB.PCI0.ISA0.EC0", /* 600e/x, 770e, 770x */
|
||||
"\\_SB.PCI0.ISA.EC", /* A21e, A2xm/p, T20-22, X20-21 */
|
||||
"\\_SB.PCI0.AD4S.EC0", /* i1400, R30 */
|
||||
"\\_SB.PCI0.ICH3.EC0", /* R31 */
|
||||
"\\_SB.PCI0.LPC.EC", /* all others */
|
||||
);
|
||||
static const acpi_handle *object##_parent __initdata = \
|
||||
&parent##_handle; \
|
||||
static char *object##_paths[] __initdata = { paths }
|
||||
|
||||
TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */
|
||||
TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */
|
||||
|
@ -528,6 +538,7 @@ TPACPI_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */
|
|||
"\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */
|
||||
"\\_SB.PCI0.VID0", /* 770e */
|
||||
"\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */
|
||||
"\\_SB.PCI0.AGP.VGA", /* X100e and a few others */
|
||||
"\\_SB.PCI0.AGP.VID", /* all others */
|
||||
); /* R30, R31 */
|
||||
|
||||
|
@ -594,9 +605,10 @@ static int acpi_evalf(acpi_handle handle,
|
|||
|
||||
switch (res_type) {
|
||||
case 'd': /* int */
|
||||
if (res)
|
||||
success = (status == AE_OK &&
|
||||
out_obj.type == ACPI_TYPE_INTEGER);
|
||||
if (success && res)
|
||||
*(int *)res = out_obj.integer.value;
|
||||
success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
|
||||
break;
|
||||
case 'v': /* void */
|
||||
success = status == AE_OK;
|
||||
|
@ -609,8 +621,8 @@ static int acpi_evalf(acpi_handle handle,
|
|||
}
|
||||
|
||||
if (!success && !quiet)
|
||||
printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
|
||||
method, fmt0, status);
|
||||
printk(TPACPI_ERR "acpi_evalf(%s, %s, ...) failed: %s\n",
|
||||
method, fmt0, acpi_format_exception(status));
|
||||
|
||||
return success;
|
||||
}
|
||||
|
@ -661,11 +673,11 @@ static int issue_thinkpad_cmos_command(int cmos_cmd)
|
|||
|
||||
#define TPACPI_ACPIHANDLE_INIT(object) \
|
||||
drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
|
||||
object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
|
||||
object##_paths, ARRAY_SIZE(object##_paths))
|
||||
|
||||
static void drv_acpi_handle_init(char *name,
|
||||
acpi_handle *handle, acpi_handle parent,
|
||||
char **paths, int num_paths, char **path)
|
||||
static void __init drv_acpi_handle_init(const char *name,
|
||||
acpi_handle *handle, const acpi_handle parent,
|
||||
char **paths, const int num_paths)
|
||||
{
|
||||
int i;
|
||||
acpi_status status;
|
||||
|
@ -676,10 +688,9 @@ static void drv_acpi_handle_init(char *name,
|
|||
for (i = 0; i < num_paths; i++) {
|
||||
status = acpi_get_handle(parent, paths[i], handle);
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
*path = paths[i];
|
||||
dbg_printk(TPACPI_DBG_INIT,
|
||||
"Found ACPI handle %s for %s\n",
|
||||
*path, name);
|
||||
paths[i], name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -689,6 +700,43 @@ static void drv_acpi_handle_init(char *name,
|
|||
*handle = NULL;
|
||||
}
|
||||
|
||||
static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
|
||||
u32 level, void *context, void **return_value)
|
||||
{
|
||||
*(acpi_handle *)return_value = handle;
|
||||
|
||||
return AE_CTRL_TERMINATE;
|
||||
}
|
||||
|
||||
static void __init tpacpi_acpi_handle_locate(const char *name,
|
||||
const char *hid,
|
||||
acpi_handle *handle)
|
||||
{
|
||||
acpi_status status;
|
||||
acpi_handle device_found;
|
||||
|
||||
BUG_ON(!name || !hid || !handle);
|
||||
vdbg_printk(TPACPI_DBG_INIT,
|
||||
"trying to locate ACPI handle for %s, using HID %s\n",
|
||||
name, hid);
|
||||
|
||||
memset(&device_found, 0, sizeof(device_found));
|
||||
status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
|
||||
(void *)name, &device_found);
|
||||
|
||||
*handle = NULL;
|
||||
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
*handle = device_found;
|
||||
dbg_printk(TPACPI_DBG_INIT,
|
||||
"Found ACPI handle for %s\n", name);
|
||||
} else {
|
||||
vdbg_printk(TPACPI_DBG_INIT,
|
||||
"Could not locate an ACPI handle for %s: %s\n",
|
||||
name, acpi_format_exception(status));
|
||||
}
|
||||
}
|
||||
|
||||
static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
|
||||
{
|
||||
struct ibm_struct *ibm = data;
|
||||
|
@ -736,8 +784,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
|||
"handling %s events\n", ibm->name);
|
||||
} else {
|
||||
printk(TPACPI_ERR
|
||||
"acpi_install_notify_handler(%s) failed: %d\n",
|
||||
ibm->name, status);
|
||||
"acpi_install_notify_handler(%s) failed: %s\n",
|
||||
ibm->name, acpi_format_exception(status));
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
|
@ -1035,80 +1083,6 @@ static void tpacpi_disable_brightness_delay(void)
|
|||
"ACPI backlight control delay disabled\n");
|
||||
}
|
||||
|
||||
static int __init tpacpi_query_bcl_levels(acpi_handle handle)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
int rc;
|
||||
|
||||
if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
|
||||
obj = (union acpi_object *)buffer.pointer;
|
||||
if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
|
||||
printk(TPACPI_ERR "Unknown _BCL data, "
|
||||
"please report this to %s\n", TPACPI_MAIL);
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = obj->package.count;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
kfree(buffer.pointer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
|
||||
u32 lvl, void *context, void **rv)
|
||||
{
|
||||
char name[ACPI_PATH_SEGMENT_LENGTH];
|
||||
struct acpi_buffer buffer = { sizeof(name), &name };
|
||||
|
||||
if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
|
||||
!strncmp("_BCL", name, sizeof(name) - 1)) {
|
||||
BUG_ON(!rv || !*rv);
|
||||
**(int **)rv = tpacpi_query_bcl_levels(handle);
|
||||
return AE_CTRL_TERMINATE;
|
||||
} else {
|
||||
return AE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
|
||||
*/
|
||||
static int __init tpacpi_check_std_acpi_brightness_support(void)
|
||||
{
|
||||
int status;
|
||||
int bcl_levels = 0;
|
||||
void *bcl_ptr = &bcl_levels;
|
||||
|
||||
if (!vid_handle) {
|
||||
TPACPI_ACPIHANDLE_INIT(vid);
|
||||
}
|
||||
if (!vid_handle)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Search for a _BCL method, and execute it. This is safe on all
|
||||
* ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
|
||||
* BIOS in ACPI backlight control mode. We do NOT have to care
|
||||
* about calling the _BCL method in an enabled video device, any
|
||||
* will do for our purposes.
|
||||
*/
|
||||
|
||||
status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
|
||||
tpacpi_acpi_walk_find_bcl, NULL, NULL,
|
||||
&bcl_ptr);
|
||||
|
||||
if (ACPI_SUCCESS(status) && bcl_levels > 2) {
|
||||
tp_features.bright_acpimode = 1;
|
||||
return (bcl_levels - 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void printk_deprecated_attribute(const char * const what,
|
||||
const char * const details)
|
||||
{
|
||||
|
@ -1872,34 +1846,9 @@ static bool __init tpacpi_is_fw_known(void)
|
|||
****************************************************************************/
|
||||
|
||||
/*************************************************************************
|
||||
* thinkpad-acpi init subdriver
|
||||
* thinkpad-acpi metadata subdriver
|
||||
*/
|
||||
|
||||
static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
|
||||
printk(TPACPI_INFO "%s\n", TPACPI_URL);
|
||||
|
||||
printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
|
||||
(thinkpad_id.bios_version_str) ?
|
||||
thinkpad_id.bios_version_str : "unknown",
|
||||
(thinkpad_id.ec_version_str) ?
|
||||
thinkpad_id.ec_version_str : "unknown");
|
||||
|
||||
if (thinkpad_id.vendor && thinkpad_id.model_str)
|
||||
printk(TPACPI_INFO "%s %s, model %s\n",
|
||||
(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
|
||||
"IBM" : ((thinkpad_id.vendor ==
|
||||
PCI_VENDOR_ID_LENOVO) ?
|
||||
"Lenovo" : "Unknown vendor"),
|
||||
thinkpad_id.model_str,
|
||||
(thinkpad_id.nummodel_str) ?
|
||||
thinkpad_id.nummodel_str : "unknown");
|
||||
|
||||
tpacpi_check_outdated_fw();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thinkpad_acpi_driver_read(struct seq_file *m)
|
||||
{
|
||||
seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
|
||||
|
@ -2405,6 +2354,36 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
|
|||
tpacpi_hotkey_send_key(__scancode); \
|
||||
} while (0)
|
||||
|
||||
void issue_volchange(const unsigned int oldvol,
|
||||
const unsigned int newvol)
|
||||
{
|
||||
unsigned int i = oldvol;
|
||||
|
||||
while (i > newvol) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
||||
i--;
|
||||
}
|
||||
while (i < newvol) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void issue_brightnesschange(const unsigned int oldbrt,
|
||||
const unsigned int newbrt)
|
||||
{
|
||||
unsigned int i = oldbrt;
|
||||
|
||||
while (i > newbrt) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
||||
i--;
|
||||
}
|
||||
while (i < newbrt) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
|
||||
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
|
||||
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
|
||||
|
@ -2414,41 +2393,61 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
|
|||
|
||||
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
|
||||
|
||||
/* handle volume */
|
||||
if (oldn->volume_toggle != newn->volume_toggle) {
|
||||
if (oldn->mute != newn->mute) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
|
||||
}
|
||||
if (oldn->volume_level > newn->volume_level) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
||||
} else if (oldn->volume_level < newn->volume_level) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
||||
} else if (oldn->mute == newn->mute) {
|
||||
/* repeated key presses that didn't change state */
|
||||
/*
|
||||
* Handle volume
|
||||
*
|
||||
* This code is supposed to duplicate the IBM firmware behaviour:
|
||||
* - Pressing MUTE issues mute hotkey message, even when already mute
|
||||
* - Pressing Volume up/down issues volume up/down hotkey messages,
|
||||
* even when already at maximum or minumum volume
|
||||
* - The act of unmuting issues volume up/down notification,
|
||||
* depending which key was used to unmute
|
||||
*
|
||||
* We are constrained to what the NVRAM can tell us, which is not much
|
||||
* and certainly not enough if more than one volume hotkey was pressed
|
||||
* since the last poll cycle.
|
||||
*
|
||||
* Just to make our life interesting, some newer Lenovo ThinkPads have
|
||||
* bugs in the BIOS and may fail to update volume_toggle properly.
|
||||
*/
|
||||
if (newn->mute) {
|
||||
/* muted */
|
||||
if (!oldn->mute ||
|
||||
oldn->volume_toggle != newn->volume_toggle ||
|
||||
oldn->volume_level != newn->volume_level) {
|
||||
/* recently muted, or repeated mute keypress, or
|
||||
* multiple presses ending in mute */
|
||||
issue_volchange(oldn->volume_level, newn->volume_level);
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
|
||||
} else if (newn->volume_level != 0) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
||||
} else {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
||||
}
|
||||
} else {
|
||||
/* unmute */
|
||||
if (oldn->mute) {
|
||||
/* recently unmuted, issue 'unmute' keypress */
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
||||
}
|
||||
if (oldn->volume_level != newn->volume_level) {
|
||||
issue_volchange(oldn->volume_level, newn->volume_level);
|
||||
} else if (oldn->volume_toggle != newn->volume_toggle) {
|
||||
/* repeated vol up/down keypress at end of scale ? */
|
||||
if (newn->volume_level == 0)
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
||||
else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
||||
}
|
||||
}
|
||||
|
||||
/* handle brightness */
|
||||
if (oldn->brightness_toggle != newn->brightness_toggle) {
|
||||
if (oldn->brightness_level < newn->brightness_level) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
||||
} else if (oldn->brightness_level > newn->brightness_level) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
||||
} else {
|
||||
if (oldn->brightness_level != newn->brightness_level) {
|
||||
issue_brightnesschange(oldn->brightness_level,
|
||||
newn->brightness_level);
|
||||
} else if (oldn->brightness_toggle != newn->brightness_toggle) {
|
||||
/* repeated key presses that didn't change state */
|
||||
if (newn->brightness_level != 0) {
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
||||
} else {
|
||||
if (newn->brightness_level == 0)
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
||||
}
|
||||
}
|
||||
else if (newn->brightness_level >= bright_maxlvl
|
||||
&& !tp_features.bright_unkfw)
|
||||
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
||||
}
|
||||
|
||||
#undef TPACPI_COMPARE_KEY
|
||||
|
@ -3353,7 +3352,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
|
|||
goto err_exit;
|
||||
}
|
||||
|
||||
if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
|
||||
if (tpacpi_is_lenovo()) {
|
||||
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
|
||||
"using Lenovo default hot key map\n");
|
||||
memcpy(hotkey_keycode_map, &lenovo_keycode_map,
|
||||
|
@ -3391,11 +3390,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
|
|||
}
|
||||
|
||||
/* Do not issue duplicate brightness change events to
|
||||
* userspace */
|
||||
if (!tp_features.bright_acpimode)
|
||||
/* update bright_acpimode... */
|
||||
tpacpi_check_std_acpi_brightness_support();
|
||||
|
||||
* userspace. tpacpi_detect_brightness_capabilities() must have
|
||||
* been called before this point */
|
||||
if (tp_features.bright_acpimode && acpi_video_backlight_support()) {
|
||||
printk(TPACPI_INFO
|
||||
"This ThinkPad has standard ACPI backlight "
|
||||
|
@ -4422,6 +4418,7 @@ static int __init video_init(struct ibm_init_struct *iibm)
|
|||
vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
|
||||
|
||||
TPACPI_ACPIHANDLE_INIT(vid);
|
||||
if (tpacpi_is_ibm())
|
||||
TPACPI_ACPIHANDLE_INIT(vid2);
|
||||
|
||||
if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
|
||||
|
@ -4431,10 +4428,12 @@ static int __init video_init(struct ibm_init_struct *iibm)
|
|||
if (!vid_handle)
|
||||
/* video switching not supported on R30, R31 */
|
||||
video_supported = TPACPI_VIDEO_NONE;
|
||||
else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
|
||||
else if (tpacpi_is_ibm() &&
|
||||
acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
|
||||
/* 570 */
|
||||
video_supported = TPACPI_VIDEO_570;
|
||||
else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
|
||||
else if (tpacpi_is_ibm() &&
|
||||
acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
|
||||
/* 600e/x, 770e, 770x */
|
||||
video_supported = TPACPI_VIDEO_770;
|
||||
else
|
||||
|
@ -4811,8 +4810,10 @@ static int __init light_init(struct ibm_init_struct *iibm)
|
|||
|
||||
vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
|
||||
|
||||
if (tpacpi_is_ibm()) {
|
||||
TPACPI_ACPIHANDLE_INIT(ledb);
|
||||
TPACPI_ACPIHANDLE_INIT(lght);
|
||||
}
|
||||
TPACPI_ACPIHANDLE_INIT(cmos);
|
||||
INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
|
||||
|
||||
|
@ -5007,11 +5008,7 @@ enum { /* For TPACPI_LED_OLD */
|
|||
|
||||
static enum led_access_mode led_supported;
|
||||
|
||||
TPACPI_HANDLE(led, ec, "SLED", /* 570 */
|
||||
"SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, */
|
||||
/* T20-22, X20-21 */
|
||||
"LED", /* all others */
|
||||
); /* R30, R31 */
|
||||
static acpi_handle led_handle;
|
||||
|
||||
#define TPACPI_LED_NUMLEDS 16
|
||||
static struct tpacpi_led_classdev *tpacpi_leds;
|
||||
|
@ -5271,6 +5268,32 @@ static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
|
|||
#undef TPACPI_LEDQ_IBM
|
||||
#undef TPACPI_LEDQ_LNV
|
||||
|
||||
static enum led_access_mode __init led_init_detect_mode(void)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
if (tpacpi_is_ibm()) {
|
||||
/* 570 */
|
||||
status = acpi_get_handle(ec_handle, "SLED", &led_handle);
|
||||
if (ACPI_SUCCESS(status))
|
||||
return TPACPI_LED_570;
|
||||
|
||||
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
||||
status = acpi_get_handle(ec_handle, "SYSL", &led_handle);
|
||||
if (ACPI_SUCCESS(status))
|
||||
return TPACPI_LED_OLD;
|
||||
}
|
||||
|
||||
/* most others */
|
||||
status = acpi_get_handle(ec_handle, "LED", &led_handle);
|
||||
if (ACPI_SUCCESS(status))
|
||||
return TPACPI_LED_NEW;
|
||||
|
||||
/* R30, R31, and unknown firmwares */
|
||||
led_handle = NULL;
|
||||
return TPACPI_LED_NONE;
|
||||
}
|
||||
|
||||
static int __init led_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
unsigned int i;
|
||||
|
@ -5279,20 +5302,7 @@ static int __init led_init(struct ibm_init_struct *iibm)
|
|||
|
||||
vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
|
||||
|
||||
TPACPI_ACPIHANDLE_INIT(led);
|
||||
|
||||
if (!led_handle)
|
||||
/* led not supported on R30, R31 */
|
||||
led_supported = TPACPI_LED_NONE;
|
||||
else if (strlencmp(led_path, "SLED") == 0)
|
||||
/* 570 */
|
||||
led_supported = TPACPI_LED_570;
|
||||
else if (strlencmp(led_path, "SYSL") == 0)
|
||||
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
||||
led_supported = TPACPI_LED_OLD;
|
||||
else
|
||||
/* all others */
|
||||
led_supported = TPACPI_LED_NEW;
|
||||
led_supported = led_init_detect_mode();
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
|
||||
str_supported(led_supported), led_supported);
|
||||
|
@ -5741,11 +5751,12 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
|
|||
TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
|
||||
}
|
||||
} else if (acpi_tmp7) {
|
||||
if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
|
||||
if (tpacpi_is_ibm() &&
|
||||
acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
|
||||
/* 600e/x, 770e, 770x */
|
||||
thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
|
||||
} else {
|
||||
/* Standard ACPI TMPx access, max 8 sensors */
|
||||
/* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */
|
||||
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
|
||||
}
|
||||
} else {
|
||||
|
@ -5954,7 +5965,7 @@ static unsigned int tpacpi_brightness_nvram_get(void)
|
|||
lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
|
||||
& TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
||||
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
|
||||
lnvram &= (tp_features.bright_16levels) ? 0x0f : 0x07;
|
||||
lnvram &= bright_maxlvl;
|
||||
|
||||
return lnvram;
|
||||
}
|
||||
|
@ -6063,8 +6074,7 @@ static int brightness_set(unsigned int value)
|
|||
{
|
||||
int res;
|
||||
|
||||
if (value > ((tp_features.bright_16levels)? 15 : 7) ||
|
||||
value < 0)
|
||||
if (value > bright_maxlvl || value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
vdbg_printk(TPACPI_DBG_BRGHT,
|
||||
|
@ -6139,6 +6149,80 @@ static struct backlight_ops ibm_backlight_data = {
|
|||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static int __init tpacpi_query_bcl_levels(acpi_handle handle)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
int rc;
|
||||
|
||||
if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
|
||||
obj = (union acpi_object *)buffer.pointer;
|
||||
if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
|
||||
printk(TPACPI_ERR "Unknown _BCL data, "
|
||||
"please report this to %s\n", TPACPI_MAIL);
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = obj->package.count;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
kfree(buffer.pointer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static acpi_status __init tpacpi_acpi_walk_find_bcl(acpi_handle handle,
|
||||
u32 lvl, void *context, void **rv)
|
||||
{
|
||||
char name[ACPI_PATH_SEGMENT_LENGTH];
|
||||
struct acpi_buffer buffer = { sizeof(name), &name };
|
||||
|
||||
if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
|
||||
!strncmp("_BCL", name, sizeof(name) - 1)) {
|
||||
BUG_ON(!rv || !*rv);
|
||||
**(int **)rv = tpacpi_query_bcl_levels(handle);
|
||||
return AE_CTRL_TERMINATE;
|
||||
} else {
|
||||
return AE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
|
||||
*/
|
||||
static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
|
||||
{
|
||||
int status;
|
||||
int bcl_levels = 0;
|
||||
void *bcl_ptr = &bcl_levels;
|
||||
|
||||
if (!vid_handle)
|
||||
TPACPI_ACPIHANDLE_INIT(vid);
|
||||
|
||||
if (!vid_handle)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Search for a _BCL method, and execute it. This is safe on all
|
||||
* ThinkPads, and as a side-effect, _BCL will place a Lenovo Vista
|
||||
* BIOS in ACPI backlight control mode. We do NOT have to care
|
||||
* about calling the _BCL method in an enabled video device, any
|
||||
* will do for our purposes.
|
||||
*/
|
||||
|
||||
status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
|
||||
tpacpi_acpi_walk_find_bcl, NULL, NULL,
|
||||
&bcl_ptr);
|
||||
|
||||
if (ACPI_SUCCESS(status) && bcl_levels > 2) {
|
||||
tp_features.bright_acpimode = 1;
|
||||
return bcl_levels - 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* These are only useful for models that have only one possibility
|
||||
* of GPU. If the BIOS model handles both ATI and Intel, don't use
|
||||
|
@ -6169,6 +6253,47 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
|
|||
TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC), /* X41 Tablet */
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns < 0 for error, otherwise sets tp_features.bright_*
|
||||
* and bright_maxlvl.
|
||||
*/
|
||||
static void __init tpacpi_detect_brightness_capabilities(void)
|
||||
{
|
||||
unsigned int b;
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT,
|
||||
"detecting firmware brightness interface capabilities\n");
|
||||
|
||||
/* we could run a quirks check here (same table used by
|
||||
* brightness_init) if needed */
|
||||
|
||||
/*
|
||||
* We always attempt to detect acpi support, so as to switch
|
||||
* Lenovo Vista BIOS to ACPI brightness mode even if we are not
|
||||
* going to publish a backlight interface
|
||||
*/
|
||||
b = tpacpi_check_std_acpi_brightness_support();
|
||||
switch (b) {
|
||||
case 16:
|
||||
bright_maxlvl = 15;
|
||||
printk(TPACPI_INFO
|
||||
"detected a 16-level brightness capable ThinkPad\n");
|
||||
break;
|
||||
case 8:
|
||||
case 0:
|
||||
bright_maxlvl = 7;
|
||||
printk(TPACPI_INFO
|
||||
"detected a 8-level brightness capable ThinkPad\n");
|
||||
break;
|
||||
default:
|
||||
printk(TPACPI_ERR
|
||||
"Unsupported brightness interface, "
|
||||
"please contact %s\n", TPACPI_MAIL);
|
||||
tp_features.bright_unkfw = 1;
|
||||
bright_maxlvl = b - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int __init brightness_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
struct backlight_properties props;
|
||||
|
@ -6182,14 +6307,13 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
|||
quirks = tpacpi_check_quirks(brightness_quirk_table,
|
||||
ARRAY_SIZE(brightness_quirk_table));
|
||||
|
||||
/*
|
||||
* We always attempt to detect acpi support, so as to switch
|
||||
* Lenovo Vista BIOS to ACPI brightness mode even if we are not
|
||||
* going to publish a backlight interface
|
||||
*/
|
||||
b = tpacpi_check_std_acpi_brightness_support();
|
||||
if (b > 0) {
|
||||
/* tpacpi_detect_brightness_capabilities() must have run already */
|
||||
|
||||
/* if it is unknown, we don't handle it: it wouldn't be safe */
|
||||
if (tp_features.bright_unkfw)
|
||||
return 1;
|
||||
|
||||
if (tp_features.bright_acpimode) {
|
||||
if (acpi_video_backlight_support()) {
|
||||
if (brightness_enable > 1) {
|
||||
printk(TPACPI_NOTICE
|
||||
|
@ -6218,15 +6342,6 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (b > 16) {
|
||||
printk(TPACPI_ERR
|
||||
"Unsupported brightness interface, "
|
||||
"please contact %s\n", TPACPI_MAIL);
|
||||
return 1;
|
||||
}
|
||||
if (b == 16)
|
||||
tp_features.bright_16levels = 1;
|
||||
|
||||
/*
|
||||
* Check for module parameter bogosity, note that we
|
||||
* init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
|
||||
|
@ -6249,7 +6364,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
|||
}
|
||||
|
||||
/* Safety */
|
||||
if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM &&
|
||||
if (!tpacpi_is_ibm() &&
|
||||
(brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
|
||||
brightness_mode == TPACPI_BRGHT_MODE_EC))
|
||||
return -EINVAL;
|
||||
|
@ -6257,12 +6372,9 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
|||
if (tpacpi_brightness_get_raw(&b) < 0)
|
||||
return 1;
|
||||
|
||||
if (tp_features.bright_16levels)
|
||||
printk(TPACPI_INFO
|
||||
"detected a 16-level brightness capable ThinkPad\n");
|
||||
|
||||
memset(&props, 0, sizeof(struct backlight_properties));
|
||||
props.max_brightness = (tp_features.bright_16levels) ? 15 : 7;
|
||||
props.max_brightness = bright_maxlvl;
|
||||
props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
|
||||
ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
|
||||
NULL, NULL,
|
||||
&ibm_backlight_data,
|
||||
|
@ -6285,7 +6397,10 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
|||
"or not on your ThinkPad\n", TPACPI_MAIL);
|
||||
}
|
||||
|
||||
ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
|
||||
/* Added by mistake in early 2007. Probably useless, but it could
|
||||
* be working around some unknown firmware problem where the value
|
||||
* read at startup doesn't match the real hardware state... so leave
|
||||
* it in place just in case */
|
||||
backlight_update_status(ibm_backlight_device);
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
|
||||
|
@ -6328,9 +6443,8 @@ static int brightness_read(struct seq_file *m)
|
|||
} else {
|
||||
seq_printf(m, "level:\t\t%d\n", level);
|
||||
seq_printf(m, "commands:\tup, down\n");
|
||||
seq_printf(m, "commands:\tlevel <level>"
|
||||
" (<level> is 0-%d)\n",
|
||||
(tp_features.bright_16levels) ? 15 : 7);
|
||||
seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
|
||||
bright_maxlvl);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -6341,7 +6455,6 @@ static int brightness_write(char *buf)
|
|||
int level;
|
||||
int rc;
|
||||
char *cmd;
|
||||
int max_level = (tp_features.bright_16levels) ? 15 : 7;
|
||||
|
||||
level = brightness_get(NULL);
|
||||
if (level < 0)
|
||||
|
@ -6349,13 +6462,13 @@ static int brightness_write(char *buf)
|
|||
|
||||
while ((cmd = next_cmd(&buf))) {
|
||||
if (strlencmp(cmd, "up") == 0) {
|
||||
if (level < max_level)
|
||||
if (level < bright_maxlvl)
|
||||
level++;
|
||||
} else if (strlencmp(cmd, "down") == 0) {
|
||||
if (level > 0)
|
||||
level--;
|
||||
} else if (sscanf(cmd, "level %d", &level) == 1 &&
|
||||
level >= 0 && level <= max_level) {
|
||||
level >= 0 && level <= bright_maxlvl) {
|
||||
/* new level set */
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
@ -6669,6 +6782,8 @@ static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
|
|||
static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
tpacpi_disclose_usertask("ALSA", "set volume to %ld\n",
|
||||
ucontrol->value.integer.value[0]);
|
||||
return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
|
||||
}
|
||||
|
||||
|
@ -6692,6 +6807,9 @@ static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
|
|||
static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
tpacpi_disclose_usertask("ALSA", "%smute\n",
|
||||
ucontrol->value.integer.value[0] ?
|
||||
"un" : "");
|
||||
return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
|
||||
}
|
||||
|
||||
|
@ -7968,9 +8086,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
|||
tp_features.second_fan = 0;
|
||||
fan_control_desired_level = 7;
|
||||
|
||||
if (tpacpi_is_ibm()) {
|
||||
TPACPI_ACPIHANDLE_INIT(fans);
|
||||
TPACPI_ACPIHANDLE_INIT(gfan);
|
||||
TPACPI_ACPIHANDLE_INIT(sfan);
|
||||
}
|
||||
|
||||
quirks = tpacpi_check_quirks(fan_quirk_table,
|
||||
ARRAY_SIZE(fan_quirk_table));
|
||||
|
@ -8662,6 +8782,10 @@ static int __init probe_for_thinkpad(void)
|
|||
if (acpi_disabled)
|
||||
return -ENODEV;
|
||||
|
||||
/* It would be dangerous to run the driver in this case */
|
||||
if (!tpacpi_is_ibm() && !tpacpi_is_lenovo())
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Non-ancient models have better DMI tagging, but very old models
|
||||
* don't. tpacpi_is_fw_known() is a cheat to help in that case.
|
||||
|
@ -8670,8 +8794,8 @@ static int __init probe_for_thinkpad(void)
|
|||
(thinkpad_id.ec_model != 0) ||
|
||||
tpacpi_is_fw_known();
|
||||
|
||||
/* ec is required because many other handles are relative to it */
|
||||
TPACPI_ACPIHANDLE_INIT(ec);
|
||||
/* The EC handler is required */
|
||||
tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
|
||||
if (!ec_handle) {
|
||||
if (is_thinkpad)
|
||||
printk(TPACPI_ERR
|
||||
|
@ -8685,12 +8809,34 @@ static int __init probe_for_thinkpad(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void __init thinkpad_acpi_init_banner(void)
|
||||
{
|
||||
printk(TPACPI_INFO "%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
|
||||
printk(TPACPI_INFO "%s\n", TPACPI_URL);
|
||||
|
||||
printk(TPACPI_INFO "ThinkPad BIOS %s, EC %s\n",
|
||||
(thinkpad_id.bios_version_str) ?
|
||||
thinkpad_id.bios_version_str : "unknown",
|
||||
(thinkpad_id.ec_version_str) ?
|
||||
thinkpad_id.ec_version_str : "unknown");
|
||||
|
||||
BUG_ON(!thinkpad_id.vendor);
|
||||
|
||||
if (thinkpad_id.model_str)
|
||||
printk(TPACPI_INFO "%s %s, model %s\n",
|
||||
(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
|
||||
"IBM" : ((thinkpad_id.vendor ==
|
||||
PCI_VENDOR_ID_LENOVO) ?
|
||||
"Lenovo" : "Unknown vendor"),
|
||||
thinkpad_id.model_str,
|
||||
(thinkpad_id.nummodel_str) ?
|
||||
thinkpad_id.nummodel_str : "unknown");
|
||||
}
|
||||
|
||||
/* Module init, exit, parameters */
|
||||
|
||||
static struct ibm_init_struct ibms_init[] __initdata = {
|
||||
{
|
||||
.init = thinkpad_acpi_driver_init,
|
||||
.data = &thinkpad_acpi_driver_data,
|
||||
},
|
||||
{
|
||||
|
@ -8960,6 +9106,9 @@ static int __init thinkpad_acpi_module_init(void)
|
|||
|
||||
/* Driver initialization */
|
||||
|
||||
thinkpad_acpi_init_banner();
|
||||
tpacpi_check_outdated_fw();
|
||||
|
||||
TPACPI_ACPIHANDLE_INIT(ecrd);
|
||||
TPACPI_ACPIHANDLE_INIT(ecwr);
|
||||
|
||||
|
@ -9059,13 +9208,16 @@ static int __init thinkpad_acpi_module_init(void)
|
|||
tpacpi_inputdev->name = "ThinkPad Extra Buttons";
|
||||
tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
|
||||
tpacpi_inputdev->id.bustype = BUS_HOST;
|
||||
tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
|
||||
thinkpad_id.vendor :
|
||||
PCI_VENDOR_ID_IBM;
|
||||
tpacpi_inputdev->id.vendor = thinkpad_id.vendor;
|
||||
tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
|
||||
tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
|
||||
tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
|
||||
}
|
||||
|
||||
/* Init subdriver dependencies */
|
||||
tpacpi_detect_brightness_capabilities();
|
||||
|
||||
/* Init subdrivers */
|
||||
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
|
||||
ret = ibm_init(&ibms_init[i]);
|
||||
if (ret >= 0 && *ibms_init[i].param)
|
||||
|
|
|
@ -81,6 +81,16 @@ static struct wmi_block wmi_blocks;
|
|||
#define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */
|
||||
#define ACPI_WMI_EVENT 0x8 /* GUID is an event */
|
||||
|
||||
static int debug_event;
|
||||
module_param(debug_event, bool, 0444);
|
||||
MODULE_PARM_DESC(debug_event,
|
||||
"Log WMI Events [0/1]");
|
||||
|
||||
static int debug_dump_wdg;
|
||||
module_param(debug_dump_wdg, bool, 0444);
|
||||
MODULE_PARM_DESC(debug_dump_wdg,
|
||||
"Dump available WMI interfaces [0/1]");
|
||||
|
||||
static int acpi_wmi_remove(struct acpi_device *device, int type);
|
||||
static int acpi_wmi_add(struct acpi_device *device);
|
||||
static void acpi_wmi_notify(struct acpi_device *device, u32 event);
|
||||
|
@ -477,6 +487,64 @@ const struct acpi_buffer *in)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_set_block);
|
||||
|
||||
static void wmi_dump_wdg(struct guid_block *g)
|
||||
{
|
||||
char guid_string[37];
|
||||
|
||||
wmi_gtoa(g->guid, guid_string);
|
||||
printk(KERN_INFO PREFIX "%s:\n", guid_string);
|
||||
printk(KERN_INFO PREFIX "\tobject_id: %c%c\n",
|
||||
g->object_id[0], g->object_id[1]);
|
||||
printk(KERN_INFO PREFIX "\tnotify_id: %02X\n", g->notify_id);
|
||||
printk(KERN_INFO PREFIX "\treserved: %02X\n", g->reserved);
|
||||
printk(KERN_INFO PREFIX "\tinstance_count: %d\n", g->instance_count);
|
||||
printk(KERN_INFO PREFIX "\tflags: %#x", g->flags);
|
||||
if (g->flags) {
|
||||
printk(" ");
|
||||
if (g->flags & ACPI_WMI_EXPENSIVE)
|
||||
printk("ACPI_WMI_EXPENSIVE ");
|
||||
if (g->flags & ACPI_WMI_METHOD)
|
||||
printk("ACPI_WMI_METHOD ");
|
||||
if (g->flags & ACPI_WMI_STRING)
|
||||
printk("ACPI_WMI_STRING ");
|
||||
if (g->flags & ACPI_WMI_EVENT)
|
||||
printk("ACPI_WMI_EVENT ");
|
||||
}
|
||||
printk("\n");
|
||||
|
||||
}
|
||||
|
||||
static void wmi_notify_debug(u32 value, void *context)
|
||||
{
|
||||
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
|
||||
wmi_get_event_data(value, &response);
|
||||
|
||||
obj = (union acpi_object *)response.pointer;
|
||||
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
printk(KERN_INFO PREFIX "DEBUG Event ");
|
||||
switch(obj->type) {
|
||||
case ACPI_TYPE_BUFFER:
|
||||
printk("BUFFER_TYPE - length %d\n", obj->buffer.length);
|
||||
break;
|
||||
case ACPI_TYPE_STRING:
|
||||
printk("STRING_TYPE - %s\n", obj->string.pointer);
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
printk("INTEGER_TYPE - %llu\n", obj->integer.value);
|
||||
break;
|
||||
case ACPI_TYPE_PACKAGE:
|
||||
printk("PACKAGE_TYPE - %d elements\n", obj->package.count);
|
||||
break;
|
||||
default:
|
||||
printk("object type 0x%X\n", obj->type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wmi_install_notify_handler - Register handler for WMI events
|
||||
* @handler: Function to handle notifications
|
||||
|
@ -496,7 +564,7 @@ wmi_notify_handler handler, void *data)
|
|||
if (!find_guid(guid, &block))
|
||||
return AE_NOT_EXIST;
|
||||
|
||||
if (block->handler)
|
||||
if (block->handler && block->handler != wmi_notify_debug)
|
||||
return AE_ALREADY_ACQUIRED;
|
||||
|
||||
block->handler = handler;
|
||||
|
@ -516,7 +584,7 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
|
|||
acpi_status wmi_remove_notify_handler(const char *guid)
|
||||
{
|
||||
struct wmi_block *block;
|
||||
acpi_status status;
|
||||
acpi_status status = AE_OK;
|
||||
|
||||
if (!guid)
|
||||
return AE_BAD_PARAMETER;
|
||||
|
@ -524,14 +592,16 @@ acpi_status wmi_remove_notify_handler(const char *guid)
|
|||
if (!find_guid(guid, &block))
|
||||
return AE_NOT_EXIST;
|
||||
|
||||
if (!block->handler)
|
||||
if (!block->handler || block->handler == wmi_notify_debug)
|
||||
return AE_NULL_ENTRY;
|
||||
|
||||
if (debug_event) {
|
||||
block->handler = wmi_notify_debug;
|
||||
} else {
|
||||
status = wmi_method_enable(block, 0);
|
||||
|
||||
block->handler = NULL;
|
||||
block->handler_data = NULL;
|
||||
|
||||
}
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
|
||||
|
@ -756,12 +826,10 @@ static __init acpi_status parse_wdg(acpi_handle handle)
|
|||
|
||||
total = obj->buffer.length / sizeof(struct guid_block);
|
||||
|
||||
gblock = kzalloc(obj->buffer.length, GFP_KERNEL);
|
||||
gblock = kmemdup(obj->buffer.pointer, obj->buffer.length, GFP_KERNEL);
|
||||
if (!gblock)
|
||||
return AE_NO_MEMORY;
|
||||
|
||||
memcpy(gblock, obj->buffer.pointer, obj->buffer.length);
|
||||
|
||||
for (i = 0; i < total; i++) {
|
||||
/*
|
||||
Some WMI devices, like those for nVidia hooks, have a
|
||||
|
@ -776,12 +844,19 @@ static __init acpi_status parse_wdg(acpi_handle handle)
|
|||
guid_string);
|
||||
continue;
|
||||
}
|
||||
if (debug_dump_wdg)
|
||||
wmi_dump_wdg(&gblock[i]);
|
||||
|
||||
wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
|
||||
if (!wblock)
|
||||
return AE_NO_MEMORY;
|
||||
|
||||
wblock->gblock = gblock[i];
|
||||
wblock->handle = handle;
|
||||
if (debug_event) {
|
||||
wblock->handler = wmi_notify_debug;
|
||||
status = wmi_method_enable(wblock, 1);
|
||||
}
|
||||
list_add_tail(&wblock->list, &wmi_blocks.list);
|
||||
}
|
||||
|
||||
|
@ -840,6 +915,7 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event)
|
|||
struct guid_block *block;
|
||||
struct wmi_block *wblock;
|
||||
struct list_head *p;
|
||||
char guid_string[37];
|
||||
|
||||
list_for_each(p, &wmi_blocks.list) {
|
||||
wblock = list_entry(p, struct wmi_block, list);
|
||||
|
@ -849,6 +925,11 @@ static void acpi_wmi_notify(struct acpi_device *device, u32 event)
|
|||
(block->notify_id == event)) {
|
||||
if (wblock->handler)
|
||||
wblock->handler(event, wblock->handler_data);
|
||||
if (debug_event) {
|
||||
wmi_gtoa(wblock->gblock.guid, guid_string);
|
||||
printk(KERN_INFO PREFIX "DEBUG Event GUID:"
|
||||
" %s\n", guid_string);
|
||||
}
|
||||
|
||||
acpi_bus_generate_netlink_event(
|
||||
device->pnp.device_class, dev_name(&device->dev),
|
||||
|
|
Загрузка…
Ссылка в новой задаче