2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2007-03-29 12:58:43 +08:00
|
|
|
* thinkpad_acpi.c - ThinkPad ACPI Extras
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
*
|
2005-08-17 12:00:00 +08:00
|
|
|
* Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
|
2007-03-24 04:33:58 +08:00
|
|
|
* Copyright (C) 2006-2007 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
2007-03-24 04:33:58 +08:00
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301, USA.
|
2005-08-17 12:00:00 +08:00
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:42 +08:00
|
|
|
#define IBM_VERSION "0.18"
|
2008-01-08 23:02:41 +08:00
|
|
|
#define TPACPI_SYSFS_VERSION 0x020101
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
/*
|
2005-04-17 06:20:36 +08:00
|
|
|
* Changelog:
|
2007-03-29 12:58:43 +08:00
|
|
|
* 2007-03-27 0.14 renamed to thinkpad_acpi and moved to
|
|
|
|
* drivers/misc.
|
2006-11-26 02:37:38 +08:00
|
|
|
*
|
|
|
|
* 2006-11-22 0.13 new maintainer
|
|
|
|
* changelog now lives in git commit history, and will
|
|
|
|
* not be updated further in-file.
|
2007-03-24 04:33:54 +08:00
|
|
|
*
|
2005-08-17 12:00:00 +08:00
|
|
|
* 2005-08-17 0.12 fix compilation on 2.6.13-rc kernels
|
|
|
|
* 2005-03-17 0.11 support for 600e, 770x
|
|
|
|
* thanks to Jamie Lentin <lentinj@dial.pipex.com>
|
|
|
|
* support for 770e, G41
|
|
|
|
* G40 and G41 don't have a thinklight
|
|
|
|
* temperatures no longer experimental
|
|
|
|
* experimental brightness control
|
|
|
|
* experimental volume control
|
|
|
|
* experimental fan enable/disable
|
2007-03-24 04:33:54 +08:00
|
|
|
* 2005-01-16 0.10 fix module loading on R30, R31
|
2005-08-17 12:00:00 +08:00
|
|
|
* 2005-01-16 0.9 support for 570, R30, R31
|
|
|
|
* ultrabay support on A22p, A3x
|
|
|
|
* limit arg for cmos, led, beep, drop experimental status
|
|
|
|
* more capable led control on A21e, A22p, T20-22, X20
|
|
|
|
* experimental temperatures and fan speed
|
|
|
|
* experimental embedded controller register dump
|
|
|
|
* mark more functions as __init, drop incorrect __exit
|
|
|
|
* use MODULE_VERSION
|
|
|
|
* thanks to Henrik Brix Andersen <brix@gentoo.org>
|
|
|
|
* fix parameter passing on module loading
|
|
|
|
* thanks to Rusty Russell <rusty@rustcorp.com.au>
|
|
|
|
* thanks to Jim Radford <radford@blackbean.org>
|
|
|
|
* 2004-11-08 0.8 fix init error case, don't return from a macro
|
|
|
|
* thanks to Chris Wright <chrisw@osdl.org>
|
|
|
|
* 2004-10-23 0.7 fix module loading on A21e, A22p, T20, T21, X20
|
|
|
|
* fix led control on A21e
|
|
|
|
* 2004-10-19 0.6 use acpi_bus_register_driver() to claim HKEY device
|
2005-04-17 06:20:36 +08:00
|
|
|
* 2004-10-18 0.5 thinklight support on A21e, G40, R32, T20, T21, X20
|
|
|
|
* proc file format changed
|
|
|
|
* video_switch command
|
|
|
|
* experimental cmos control
|
|
|
|
* experimental led control
|
|
|
|
* experimental acpi sounds
|
2005-08-17 12:00:00 +08:00
|
|
|
* 2004-09-16 0.4 support for module parameters
|
|
|
|
* hotkey mask can be prefixed by 0x
|
|
|
|
* video output switching
|
|
|
|
* video expansion control
|
|
|
|
* ultrabay eject support
|
|
|
|
* removed lcd brightness/on/off control, didn't work
|
|
|
|
* 2004-08-17 0.3 support for R40
|
|
|
|
* lcd off, brightness control
|
|
|
|
* thinklight on/off
|
|
|
|
* 2004-08-14 0.2 support for T series, X20
|
|
|
|
* bluetooth enable/disable
|
|
|
|
* hotkey events disabled by default
|
|
|
|
* removed fan control, currently useless
|
|
|
|
* 2004-08-09 0.1 initial release, support for X series
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:43 +08:00
|
|
|
/* ==================================================== BEGIN HEADER */
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/freezer.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
|
|
|
|
#include <linux/nvram.h>
|
|
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/sysfs.h>
|
|
|
|
#include <linux/backlight.h>
|
|
|
|
#include <linux/fb.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/hwmon.h>
|
|
|
|
#include <linux/hwmon-sysfs.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
#include <acpi/acpi_drivers.h>
|
|
|
|
#include <acpi/acnamesp.h>
|
|
|
|
|
|
|
|
#include <linux/pci_ids.h>
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Main driver
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define IBM_NAME "thinkpad"
|
|
|
|
#define IBM_DESC "ThinkPad ACPI Extras"
|
|
|
|
#define IBM_FILE IBM_NAME "_acpi"
|
|
|
|
#define IBM_URL "http://ibm-acpi.sf.net/"
|
|
|
|
#define IBM_MAIL "ibm-acpi-devel@lists.sourceforge.net"
|
|
|
|
|
|
|
|
#define IBM_PROC_DIR "ibm"
|
|
|
|
#define IBM_ACPI_EVENT_PREFIX "ibm"
|
|
|
|
#define IBM_DRVR_NAME IBM_FILE
|
|
|
|
#define IBM_HWMON_DRVR_NAME IBM_NAME "_hwmon"
|
|
|
|
|
|
|
|
#define IBM_LOG IBM_FILE ": "
|
|
|
|
#define IBM_ERR KERN_ERR IBM_LOG
|
|
|
|
#define IBM_NOTICE KERN_NOTICE IBM_LOG
|
|
|
|
#define IBM_INFO KERN_INFO IBM_LOG
|
|
|
|
#define IBM_DEBUG KERN_DEBUG IBM_LOG
|
|
|
|
|
|
|
|
#define IBM_MAX_ACPI_ARGS 3
|
|
|
|
|
|
|
|
/* ThinkPad CMOS commands */
|
|
|
|
#define TP_CMOS_VOLUME_DOWN 0
|
|
|
|
#define TP_CMOS_VOLUME_UP 1
|
|
|
|
#define TP_CMOS_VOLUME_MUTE 2
|
|
|
|
#define TP_CMOS_BRIGHTNESS_UP 4
|
|
|
|
#define TP_CMOS_BRIGHTNESS_DOWN 5
|
|
|
|
|
|
|
|
/* NVRAM Addresses */
|
|
|
|
enum tp_nvram_addr {
|
|
|
|
TP_NVRAM_ADDR_HK2 = 0x57,
|
|
|
|
TP_NVRAM_ADDR_THINKLIGHT = 0x58,
|
|
|
|
TP_NVRAM_ADDR_VIDEO = 0x59,
|
|
|
|
TP_NVRAM_ADDR_BRIGHTNESS = 0x5e,
|
|
|
|
TP_NVRAM_ADDR_MIXER = 0x60,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* NVRAM bit masks */
|
|
|
|
enum {
|
|
|
|
TP_NVRAM_MASK_HKT_THINKPAD = 0x08,
|
|
|
|
TP_NVRAM_MASK_HKT_ZOOM = 0x20,
|
|
|
|
TP_NVRAM_MASK_HKT_DISPLAY = 0x40,
|
|
|
|
TP_NVRAM_MASK_HKT_HIBERNATE = 0x80,
|
|
|
|
TP_NVRAM_MASK_THINKLIGHT = 0x10,
|
|
|
|
TP_NVRAM_MASK_HKT_DISPEXPND = 0x30,
|
|
|
|
TP_NVRAM_MASK_HKT_BRIGHTNESS = 0x20,
|
|
|
|
TP_NVRAM_MASK_LEVEL_BRIGHTNESS = 0x0f,
|
|
|
|
TP_NVRAM_POS_LEVEL_BRIGHTNESS = 0,
|
|
|
|
TP_NVRAM_MASK_MUTE = 0x40,
|
|
|
|
TP_NVRAM_MASK_HKT_VOLUME = 0x80,
|
|
|
|
TP_NVRAM_MASK_LEVEL_VOLUME = 0x0f,
|
|
|
|
TP_NVRAM_POS_LEVEL_VOLUME = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
|
|
|
|
#define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
|
|
|
|
#define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
|
|
|
|
|
|
|
|
/* Debugging */
|
|
|
|
#define TPACPI_DBG_ALL 0xffff
|
|
|
|
#define TPACPI_DBG_ALL 0xffff
|
|
|
|
#define TPACPI_DBG_INIT 0x0001
|
|
|
|
#define TPACPI_DBG_EXIT 0x0002
|
|
|
|
#define dbg_printk(a_dbg_level, format, arg...) \
|
|
|
|
do { if (dbg_level & a_dbg_level) \
|
|
|
|
printk(IBM_DEBUG "%s: " format, __func__ , ## arg); } while (0)
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
|
|
|
|
#define vdbg_printk(a_dbg_level, format, arg...) \
|
|
|
|
dbg_printk(a_dbg_level, format, ## arg)
|
|
|
|
static const char *str_supported(int is_supported);
|
|
|
|
#else
|
|
|
|
#define vdbg_printk(a_dbg_level, format, arg...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Input IDs */
|
|
|
|
#define TPACPI_HKEY_INPUT_VENDOR PCI_VENDOR_ID_IBM
|
|
|
|
#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
|
|
|
|
#define TPACPI_HKEY_INPUT_VERSION 0x4101
|
|
|
|
|
|
|
|
/* ACPI HIDs */
|
|
|
|
#define IBM_HKEY_HID "IBM0068"
|
|
|
|
|
|
|
|
/* ACPI helpers */
|
|
|
|
static int __must_check acpi_evalf(acpi_handle handle,
|
|
|
|
void *res, char *method, char *fmt, ...);
|
|
|
|
static int __must_check acpi_ec_read(int i, u8 * p);
|
|
|
|
static int __must_check acpi_ec_write(int i, u8 v);
|
|
|
|
static int __must_check _sta(acpi_handle handle);
|
|
|
|
|
|
|
|
/* ACPI handles */
|
|
|
|
static acpi_handle root_handle; /* root namespace */
|
|
|
|
static acpi_handle ec_handle; /* EC */
|
|
|
|
static acpi_handle ecrd_handle, ecwr_handle; /* 570 EC access */
|
|
|
|
static acpi_handle cmos_handle, hkey_handle; /* basic thinkpad handles */
|
|
|
|
|
|
|
|
static void drv_acpi_handle_init(char *name,
|
|
|
|
acpi_handle *handle, acpi_handle parent,
|
|
|
|
char **paths, int num_paths, char **path);
|
|
|
|
#define IBM_ACPIHANDLE_INIT(object) \
|
|
|
|
drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
|
|
|
|
object##_paths, ARRAY_SIZE(object##_paths), &object##_path)
|
|
|
|
|
|
|
|
/* ThinkPad ACPI helpers */
|
|
|
|
static int issue_thinkpad_cmos_command(int cmos_cmd);
|
|
|
|
|
|
|
|
/* procfs support */
|
|
|
|
static struct proc_dir_entry *proc_dir;
|
|
|
|
|
|
|
|
/* procfs helpers */
|
|
|
|
static int dispatch_procfs_read(char *page, char **start, off_t off,
|
|
|
|
int count, int *eof, void *data);
|
|
|
|
static int dispatch_procfs_write(struct file *file,
|
|
|
|
const char __user * userbuf,
|
|
|
|
unsigned long count, void *data);
|
|
|
|
static char *next_cmd(char **cmds);
|
|
|
|
|
|
|
|
/* sysfs support */
|
|
|
|
struct attribute_set {
|
|
|
|
unsigned int members, max_members;
|
|
|
|
struct attribute_group group;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_set *create_attr_set(unsigned int max_members,
|
|
|
|
const char* name);
|
|
|
|
#define destroy_attr_set(_set) \
|
|
|
|
kfree(_set);
|
|
|
|
static int add_to_attr_set(struct attribute_set* s, struct attribute *attr);
|
|
|
|
static int add_many_to_attr_set(struct attribute_set* s,
|
|
|
|
struct attribute **attr,
|
|
|
|
unsigned int count);
|
|
|
|
#define register_attr_set_with_sysfs(_attr_set, _kobj) \
|
|
|
|
sysfs_create_group(_kobj, &_attr_set->group)
|
|
|
|
static void delete_attr_set(struct attribute_set* s, struct kobject *kobj);
|
|
|
|
|
|
|
|
static int parse_strtoul(const char *buf, unsigned long max,
|
|
|
|
unsigned long *value);
|
|
|
|
|
|
|
|
/* Device model */
|
|
|
|
static struct platform_device *tpacpi_pdev;
|
|
|
|
static struct platform_device *tpacpi_sensors_pdev;
|
|
|
|
static struct device *tpacpi_hwmon;
|
|
|
|
static struct platform_driver tpacpi_pdriver;
|
|
|
|
static struct input_dev *tpacpi_inputdev;
|
|
|
|
static int tpacpi_create_driver_attributes(struct device_driver *drv);
|
|
|
|
static void tpacpi_remove_driver_attributes(struct device_driver *drv);
|
|
|
|
|
|
|
|
/* Module */
|
|
|
|
static int experimental;
|
|
|
|
static u32 dbg_level;
|
|
|
|
static int force_load;
|
|
|
|
static unsigned int hotkey_report_mode;
|
|
|
|
|
|
|
|
static int thinkpad_acpi_module_init(void);
|
|
|
|
static void thinkpad_acpi_module_exit(void);
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Subdrivers
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct ibm_struct;
|
|
|
|
|
|
|
|
struct tp_acpi_drv_struct {
|
|
|
|
const struct acpi_device_id *hid;
|
|
|
|
struct acpi_driver *driver;
|
|
|
|
|
|
|
|
void (*notify) (struct ibm_struct *, u32);
|
|
|
|
acpi_handle *handle;
|
|
|
|
u32 type;
|
|
|
|
struct acpi_device *device;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ibm_struct {
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
int (*read) (char *);
|
|
|
|
int (*write) (char *);
|
|
|
|
void (*exit) (void);
|
|
|
|
void (*resume) (void);
|
|
|
|
|
|
|
|
struct list_head all_drivers;
|
|
|
|
|
|
|
|
struct tp_acpi_drv_struct *acpi;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
u8 acpi_driver_registered:1;
|
|
|
|
u8 acpi_notify_installed:1;
|
|
|
|
u8 proc_created:1;
|
|
|
|
u8 init_called:1;
|
|
|
|
u8 experimental:1;
|
|
|
|
} flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ibm_init_struct {
|
|
|
|
char param[32];
|
|
|
|
|
|
|
|
int (*init) (struct ibm_init_struct *);
|
|
|
|
struct ibm_struct *data;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_BAY
|
|
|
|
u32 bay_status:1;
|
|
|
|
u32 bay_eject:1;
|
|
|
|
u32 bay_status2:1;
|
|
|
|
u32 bay_eject2:1;
|
|
|
|
#endif
|
|
|
|
u32 bluetooth:1;
|
|
|
|
u32 hotkey:1;
|
|
|
|
u32 hotkey_mask:1;
|
|
|
|
u32 hotkey_wlsw:1;
|
|
|
|
u32 light:1;
|
|
|
|
u32 light_status:1;
|
|
|
|
u32 bright_16levels:1;
|
|
|
|
u32 wan:1;
|
|
|
|
u32 fan_ctrl_status_undef:1;
|
|
|
|
u32 input_device_registered:1;
|
|
|
|
u32 platform_drv_registered:1;
|
|
|
|
u32 platform_drv_attrs_registered:1;
|
|
|
|
u32 sensors_pdrv_registered:1;
|
|
|
|
u32 sensors_pdrv_attrs_registered:1;
|
|
|
|
u32 sensors_pdev_attrs_registered:1;
|
|
|
|
u32 hotkey_poll_active:1;
|
|
|
|
} tp_features;
|
|
|
|
|
|
|
|
struct thinkpad_id_data {
|
|
|
|
unsigned int vendor; /* ThinkPad vendor:
|
|
|
|
* PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */
|
|
|
|
|
|
|
|
char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
|
|
|
|
char *ec_version_str; /* Something like 1ZHT51WW-1.04a */
|
|
|
|
|
|
|
|
u16 bios_model; /* Big Endian, TP-1Y = 0x5931, 0 = unknown */
|
|
|
|
u16 ec_model;
|
|
|
|
|
|
|
|
char *model_str;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct thinkpad_id_data thinkpad_id;
|
|
|
|
|
|
|
|
static struct list_head tpacpi_all_drivers;
|
|
|
|
|
|
|
|
static struct ibm_init_struct ibms_init[];
|
|
|
|
static int set_ibm_param(const char *val, struct kernel_param *kp);
|
|
|
|
static int ibm_init(struct ibm_init_struct *iibm);
|
|
|
|
static void ibm_exit(struct ibm_struct *ibm);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* procfs master subdriver
|
|
|
|
*/
|
|
|
|
static int thinkpad_acpi_driver_init(struct ibm_init_struct *iibm);
|
|
|
|
static int thinkpad_acpi_driver_read(char *p);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bay subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_BAY
|
|
|
|
static acpi_handle bay_handle, bay_ej_handle;
|
|
|
|
static acpi_handle bay2_handle, bay2_ej_handle;
|
|
|
|
|
|
|
|
static int bay_init(struct ibm_init_struct *iibm);
|
|
|
|
static void bay_notify(struct ibm_struct *ibm, u32 event);
|
|
|
|
static int bay_read(char *p);
|
|
|
|
static int bay_write(char *buf);
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_BAY */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Beep subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
static acpi_handle beep_handle;
|
|
|
|
|
|
|
|
static int beep_read(char *p);
|
|
|
|
static int beep_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bluetooth subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum {
|
|
|
|
/* ACPI GBDC/SBDC bits */
|
|
|
|
TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */
|
|
|
|
TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */
|
|
|
|
TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */
|
|
|
|
};
|
|
|
|
|
|
|
|
static int bluetooth_init(struct ibm_init_struct *iibm);
|
|
|
|
static int bluetooth_get_radiosw(void);
|
|
|
|
static int bluetooth_set_radiosw(int radio_on);
|
|
|
|
static int bluetooth_read(char *p);
|
|
|
|
static int bluetooth_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Brightness (backlight) subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
|
|
|
|
|
|
|
|
static struct backlight_device *ibm_backlight_device;
|
|
|
|
static int brightness_offset = 0x31;
|
|
|
|
static int brightness_mode;
|
|
|
|
static unsigned int brightness_enable; /* 0 = no, 1 = yes, 2 = auto */
|
|
|
|
|
|
|
|
static int brightness_init(struct ibm_init_struct *iibm);
|
|
|
|
static void brightness_exit(void);
|
|
|
|
static int brightness_get(struct backlight_device *bd);
|
|
|
|
static int brightness_set(int value);
|
|
|
|
static int brightness_update_status(struct backlight_device *bd);
|
|
|
|
static int brightness_read(char *p);
|
|
|
|
static int brightness_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CMOS subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int cmos_read(char *p);
|
|
|
|
static int cmos_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dock subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DOCK
|
|
|
|
static acpi_handle pci_handle;
|
|
|
|
static acpi_handle dock_handle;
|
|
|
|
|
|
|
|
static void dock_notify(struct ibm_struct *ibm, u32 event);
|
|
|
|
static int dock_read(char *p);
|
|
|
|
static int dock_write(char *buf);
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_DOCK */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* EC dump subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ecdump_read(char *p) ;
|
|
|
|
static int ecdump_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fan subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum { /* Fan control constants */
|
|
|
|
fan_status_offset = 0x2f, /* EC register 0x2f */
|
|
|
|
fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM)
|
|
|
|
* 0x84 must be read before 0x85 */
|
|
|
|
|
|
|
|
TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
|
|
|
|
TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
|
|
|
|
|
|
|
|
TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum fan_status_access_mode {
|
|
|
|
TPACPI_FAN_NONE = 0, /* No fan status or control */
|
|
|
|
TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */
|
|
|
|
TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum fan_control_access_mode {
|
|
|
|
TPACPI_FAN_WR_NONE = 0, /* No fan control */
|
|
|
|
TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */
|
|
|
|
TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */
|
|
|
|
TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum fan_control_commands {
|
|
|
|
TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */
|
|
|
|
TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */
|
|
|
|
TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd,
|
|
|
|
* and also watchdog cmd */
|
|
|
|
};
|
|
|
|
|
|
|
|
static int fan_control_allowed;
|
|
|
|
|
|
|
|
static enum fan_status_access_mode fan_status_access_mode;
|
|
|
|
static enum fan_control_access_mode fan_control_access_mode;
|
|
|
|
static enum fan_control_commands fan_control_commands;
|
|
|
|
static u8 fan_control_initial_status;
|
|
|
|
static u8 fan_control_desired_level;
|
|
|
|
static int fan_watchdog_maxinterval;
|
|
|
|
|
|
|
|
static struct mutex fan_mutex;
|
|
|
|
|
|
|
|
static acpi_handle fans_handle, gfan_handle, sfan_handle;
|
|
|
|
|
|
|
|
static int fan_init(struct ibm_init_struct *iibm);
|
|
|
|
static void fan_exit(void);
|
|
|
|
static int fan_get_status(u8 *status);
|
|
|
|
static int fan_get_status_safe(u8 *status);
|
|
|
|
static int fan_get_speed(unsigned int *speed);
|
|
|
|
static void fan_update_desired_level(u8 status);
|
|
|
|
static void fan_watchdog_fire(struct work_struct *ignored);
|
|
|
|
static void fan_watchdog_reset(void);
|
|
|
|
static int fan_set_level(int level);
|
|
|
|
static int fan_set_level_safe(int level);
|
|
|
|
static int fan_set_enable(void);
|
|
|
|
static int fan_set_disable(void);
|
|
|
|
static int fan_set_speed(int speed);
|
|
|
|
static int fan_read(char *p);
|
|
|
|
static int fan_write(char *buf);
|
|
|
|
static int fan_write_cmd_level(const char *cmd, int *rc);
|
|
|
|
static int fan_write_cmd_enable(const char *cmd, int *rc);
|
|
|
|
static int fan_write_cmd_disable(const char *cmd, int *rc);
|
|
|
|
static int fan_write_cmd_speed(const char *cmd, int *rc);
|
|
|
|
static int fan_write_cmd_watchdog(const char *cmd, int *rc);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hotkey subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum { /* hot key scan codes (derived from ACPI DSDT) */
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF1 = 0,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF2,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF3,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF4,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF5,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF6,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF7,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF8,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF9,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF10,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF11,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNF12,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNBACKSPACE,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNINSERT,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNDELETE,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNHOME,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNEND,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNPAGEUP,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNPAGEDOWN,
|
|
|
|
TP_ACPI_HOTKEYSCAN_FNSPACE,
|
|
|
|
TP_ACPI_HOTKEYSCAN_VOLUMEUP,
|
|
|
|
TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
|
|
|
|
TP_ACPI_HOTKEYSCAN_MUTE,
|
|
|
|
TP_ACPI_HOTKEYSCAN_THINKPAD,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int hotkey_orig_status;
|
|
|
|
static u32 hotkey_orig_mask;
|
|
|
|
|
|
|
|
static struct mutex hotkey_mutex;
|
|
|
|
|
|
|
|
static int hotkey_init(struct ibm_init_struct *iibm);
|
|
|
|
static void hotkey_exit(void);
|
|
|
|
static void hotkey_notify(struct ibm_struct *ibm, u32 event);
|
|
|
|
static int hotkey_read(char *p);
|
|
|
|
static int hotkey_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LED subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum led_access_mode {
|
|
|
|
TPACPI_LED_NONE = 0,
|
|
|
|
TPACPI_LED_570, /* 570 */
|
|
|
|
TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
|
|
|
TPACPI_LED_NEW, /* all others */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* For TPACPI_LED_OLD */
|
|
|
|
TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */
|
|
|
|
TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */
|
|
|
|
TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum led_access_mode led_supported;
|
|
|
|
static acpi_handle led_handle;
|
|
|
|
|
|
|
|
static int led_init(struct ibm_init_struct *iibm);
|
|
|
|
static int led_read(char *p);
|
|
|
|
static int led_write(char *buf);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Light (thinklight) subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
static acpi_handle lght_handle, ledb_handle;
|
|
|
|
|
|
|
|
static int light_init(struct ibm_init_struct *iibm);
|
|
|
|
static int light_read(char *p);
|
|
|
|
static int light_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Thermal subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum thermal_access_mode {
|
|
|
|
TPACPI_THERMAL_NONE = 0, /* No thermal support */
|
|
|
|
TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */
|
|
|
|
TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */
|
|
|
|
TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */
|
|
|
|
TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* TPACPI_THERMAL_TPEC_* */
|
|
|
|
TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */
|
|
|
|
TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */
|
|
|
|
TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */
|
|
|
|
};
|
|
|
|
|
|
|
|
#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
|
|
|
|
struct ibm_thermal_sensors_struct {
|
|
|
|
s32 temp[TPACPI_MAX_THERMAL_SENSORS];
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum thermal_access_mode thermal_read_mode;
|
|
|
|
|
|
|
|
static int thermal_init(struct ibm_init_struct *iibm);
|
|
|
|
static int thermal_get_sensor(int idx, s32 *value);
|
|
|
|
static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s);
|
|
|
|
static int thermal_read(char *p);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Video subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum video_access_mode {
|
|
|
|
TPACPI_VIDEO_NONE = 0,
|
|
|
|
TPACPI_VIDEO_570, /* 570 */
|
|
|
|
TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */
|
|
|
|
TPACPI_VIDEO_NEW, /* all others */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* video status flags, based on VIDEO_570 */
|
|
|
|
TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */
|
|
|
|
TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */
|
|
|
|
TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* TPACPI_VIDEO_570 constants */
|
|
|
|
TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */
|
|
|
|
TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to
|
|
|
|
* video_status_flags */
|
|
|
|
TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */
|
|
|
|
TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum video_access_mode video_supported;
|
|
|
|
static int video_orig_autosw;
|
|
|
|
static acpi_handle vid_handle, vid2_handle;
|
|
|
|
|
|
|
|
static int video_init(struct ibm_init_struct *iibm);
|
|
|
|
static void video_exit(void);
|
|
|
|
static int video_outputsw_get(void);
|
|
|
|
static int video_outputsw_set(int status);
|
|
|
|
static int video_autosw_get(void);
|
|
|
|
static int video_autosw_set(int enable);
|
|
|
|
static int video_outputsw_cycle(void);
|
|
|
|
static int video_expand_toggle(void);
|
|
|
|
static int video_read(char *p);
|
|
|
|
static int video_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Volume subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int volume_offset = 0x30;
|
|
|
|
|
|
|
|
static int volume_read(char *p);
|
|
|
|
static int volume_write(char *buf);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wan subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
enum {
|
|
|
|
/* ACPI GWAN/SWAN bits */
|
|
|
|
TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */
|
|
|
|
TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */
|
|
|
|
TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */
|
|
|
|
};
|
|
|
|
|
|
|
|
static int wan_init(struct ibm_init_struct *iibm);
|
|
|
|
static int wan_get_radiosw(void);
|
|
|
|
static int wan_set_radiosw(int radio_on);
|
|
|
|
static int wan_read(char *p);
|
|
|
|
static int wan_write(char *buf);
|
|
|
|
|
|
|
|
/* ==================================================== END HEADER */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-11-26 02:37:38 +08:00
|
|
|
MODULE_AUTHOR("Borislav Deianov, Henrique de Moraes Holschuh");
|
2005-08-17 12:00:00 +08:00
|
|
|
MODULE_DESCRIPTION(IBM_DESC);
|
|
|
|
MODULE_VERSION(IBM_VERSION);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
2007-03-29 12:58:42 +08:00
|
|
|
/* Please remove this in year 2009 */
|
|
|
|
MODULE_ALIAS("ibm_acpi");
|
|
|
|
|
2007-07-19 10:45:26 +08:00
|
|
|
/*
|
|
|
|
* DMI matching for module autoloading
|
|
|
|
*
|
|
|
|
* See http://thinkwiki.org/wiki/List_of_DMI_IDs
|
|
|
|
* See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
|
|
|
|
*
|
|
|
|
* Only models listed in thinkwiki will be supported, so add yours
|
|
|
|
* if it is not there yet.
|
|
|
|
*/
|
|
|
|
#define IBM_BIOS_MODULE_ALIAS(__type) \
|
|
|
|
MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW")
|
|
|
|
|
|
|
|
/* Non-ancient thinkpads */
|
|
|
|
MODULE_ALIAS("dmi:bvnIBM:*:svnIBM:*:pvrThinkPad*:rvnIBM:*");
|
|
|
|
MODULE_ALIAS("dmi:bvnLENOVO:*:svnLENOVO:*:pvrThinkPad*:rvnLENOVO:*");
|
|
|
|
|
|
|
|
/* Ancient thinkpad BIOSes have to be identified by
|
|
|
|
* BIOS type or model number, and there are far less
|
|
|
|
* BIOS types than model numbers... */
|
|
|
|
IBM_BIOS_MODULE_ALIAS("I[B,D,H,I,M,N,O,T,W,V,Y,Z]");
|
|
|
|
IBM_BIOS_MODULE_ALIAS("1[0,3,6,8,A-G,I,K,M-P,S,T]");
|
|
|
|
IBM_BIOS_MODULE_ALIAS("K[U,X-Z]");
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#define __unused __attribute__ ((unused))
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
static enum {
|
|
|
|
TPACPI_LIFE_INIT = 0,
|
|
|
|
TPACPI_LIFE_RUNNING,
|
|
|
|
TPACPI_LIFE_EXITING,
|
|
|
|
} tpacpi_lifecycle;
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* ACPI Helpers and device model
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* ACPI basic handles
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static acpi_handle root_handle;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
#define IBM_HANDLE(object, parent, paths...) \
|
|
|
|
static acpi_handle object##_handle; \
|
|
|
|
static acpi_handle *object##_parent = &parent##_handle; \
|
2005-08-17 12:00:00 +08:00
|
|
|
static char *object##_path; \
|
2005-04-17 06:20:36 +08:00
|
|
|
static char *object##_paths[] = { paths }
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
IBM_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 */
|
2007-03-24 04:33:57 +08:00
|
|
|
);
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(ecrd, ec, "ECRD"); /* 570 */
|
|
|
|
IBM_HANDLE(ecwr, ec, "ECWR"); /* 570 */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* Misc ACPI handles
|
|
|
|
*/
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
IBM_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, T4x, X31, X40 */
|
|
|
|
"\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */
|
|
|
|
"\\CMS", /* R40, R40e */
|
2007-03-24 04:33:57 +08:00
|
|
|
); /* all others */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
IBM_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
|
|
|
|
"^HKEY", /* R30, R31 */
|
|
|
|
"HKEY", /* all others */
|
2007-03-24 04:33:57 +08:00
|
|
|
); /* 570 */
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* ACPI helpers
|
2006-11-24 21:47:11 +08:00
|
|
|
*/
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int acpi_evalf(acpi_handle handle,
|
|
|
|
void *res, char *method, char *fmt, ...)
|
|
|
|
{
|
|
|
|
char *fmt0 = fmt;
|
2005-08-17 12:00:00 +08:00
|
|
|
struct acpi_object_list params;
|
|
|
|
union acpi_object in_objs[IBM_MAX_ACPI_ARGS];
|
|
|
|
struct acpi_buffer result, *resultp;
|
|
|
|
union acpi_object out_obj;
|
|
|
|
acpi_status status;
|
|
|
|
va_list ap;
|
|
|
|
char res_type;
|
|
|
|
int success;
|
|
|
|
int quiet;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!*fmt) {
|
|
|
|
printk(IBM_ERR "acpi_evalf() called with empty format\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*fmt == 'q') {
|
|
|
|
quiet = 1;
|
|
|
|
fmt++;
|
|
|
|
} else
|
|
|
|
quiet = 0;
|
|
|
|
|
|
|
|
res_type = *(fmt++);
|
|
|
|
|
|
|
|
params.count = 0;
|
|
|
|
params.pointer = &in_objs[0];
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
while (*fmt) {
|
|
|
|
char c = *(fmt++);
|
|
|
|
switch (c) {
|
|
|
|
case 'd': /* int */
|
|
|
|
in_objs[params.count].integer.value = va_arg(ap, int);
|
|
|
|
in_objs[params.count++].type = ACPI_TYPE_INTEGER;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
/* add more types as needed */
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
|
|
|
printk(IBM_ERR "acpi_evalf() called "
|
|
|
|
"with invalid format character '%c'\n", c);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (res_type != 'v') {
|
|
|
|
result.length = sizeof(out_obj);
|
|
|
|
result.pointer = &out_obj;
|
|
|
|
resultp = &result;
|
|
|
|
} else
|
|
|
|
resultp = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
status = acpi_evaluate_object(handle, method, ¶ms, resultp);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
switch (res_type) {
|
2005-08-17 12:00:00 +08:00
|
|
|
case 'd': /* int */
|
2005-04-17 06:20:36 +08:00
|
|
|
if (res)
|
|
|
|
*(int *)res = out_obj.integer.value;
|
|
|
|
success = status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
case 'v': /* void */
|
2005-04-17 06:20:36 +08:00
|
|
|
success = status == AE_OK;
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
/* add more types as needed */
|
2005-04-17 06:20:36 +08:00
|
|
|
default:
|
|
|
|
printk(IBM_ERR "acpi_evalf() called "
|
|
|
|
"with invalid format character '%c'\n", res_type);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
if (!success && !quiet)
|
|
|
|
printk(IBM_ERR "acpi_evalf(%s, %s, ...) failed: %d\n",
|
|
|
|
method, fmt0, status);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __unused acpi_print_int(acpi_handle handle, char *method)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (acpi_evalf(handle, &i, method, "d"))
|
|
|
|
printk(IBM_INFO "%s = 0x%x\n", method, i);
|
|
|
|
else
|
|
|
|
printk(IBM_ERR "error calling %s\n", method);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int acpi_ec_read(int i, u8 * p)
|
|
|
|
{
|
|
|
|
int v;
|
|
|
|
|
|
|
|
if (ecrd_handle) {
|
|
|
|
if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
|
|
|
|
return 0;
|
|
|
|
*p = v;
|
|
|
|
} else {
|
|
|
|
if (ec_read(i, p) < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int acpi_ec_write(int i, u8 v)
|
|
|
|
{
|
|
|
|
if (ecwr_handle) {
|
|
|
|
if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (ec_write(i, v) < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int _sta(acpi_handle handle)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!handle || !acpi_evalf(handle, &status, "_STA", "d"))
|
|
|
|
status = 0;
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
static int issue_thinkpad_cmos_command(int cmos_cmd)
|
|
|
|
{
|
|
|
|
if (!cmos_handle)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* ACPI device model
|
|
|
|
*/
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static void drv_acpi_handle_init(char *name,
|
2007-04-21 22:08:31 +08:00
|
|
|
acpi_handle *handle, acpi_handle parent,
|
|
|
|
char **paths, int num_paths, char **path)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
acpi_status status;
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
|
|
|
|
name);
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
for (i = 0; i < num_paths; i++) {
|
|
|
|
status = acpi_get_handle(parent, paths[i], handle);
|
|
|
|
if (ACPI_SUCCESS(status)) {
|
|
|
|
*path = paths[i];
|
2007-04-28 09:00:14 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"Found ACPI handle %s for %s\n",
|
|
|
|
*path, name);
|
2007-03-24 04:33:57 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
|
|
|
|
name);
|
2007-03-24 04:33:57 +08:00
|
|
|
*handle = NULL;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm = data;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (!ibm || !ibm->acpi || !ibm->acpi->notify)
|
2007-03-24 04:33:57 +08:00
|
|
|
return;
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->acpi->notify(ibm, event);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
acpi_status status;
|
2007-04-28 09:00:14 +08:00
|
|
|
int rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
|
|
|
|
if (!*ibm->acpi->handle)
|
2007-03-24 04:33:57 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
2007-04-21 22:08:32 +08:00
|
|
|
"setting up ACPI notify for %s\n", ibm->name);
|
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
|
|
|
|
if (rc < 0) {
|
|
|
|
printk(IBM_ERR "acpi_bus_get_device(%s) failed: %d\n",
|
|
|
|
ibm->name, rc);
|
2007-03-24 04:33:57 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
acpi_driver_data(ibm->acpi->device) = ibm;
|
|
|
|
sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
|
2007-03-29 12:58:43 +08:00
|
|
|
IBM_ACPI_EVENT_PREFIX,
|
|
|
|
ibm->name);
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
status = acpi_install_notify_handler(*ibm->acpi->handle,
|
|
|
|
ibm->acpi->type, dispatch_acpi_notify, ibm);
|
2007-03-24 04:33:57 +08:00
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
|
if (status == AE_ALREADY_EXISTS) {
|
|
|
|
printk(IBM_NOTICE "another device driver is already handling %s events\n",
|
|
|
|
ibm->name);
|
|
|
|
} else {
|
|
|
|
printk(IBM_ERR "acpi_install_notify_handler(%s) failed: %d\n",
|
|
|
|
ibm->name, status);
|
|
|
|
}
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->flags.acpi_notify_installed = 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int __init tpacpi_device_add(struct acpi_device *device)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:25 +08:00
|
|
|
static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2007-04-28 09:00:14 +08:00
|
|
|
int rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"registering %s as an ACPI driver\n", ibm->name);
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
|
|
|
|
ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
|
|
|
|
if (!ibm->acpi->driver) {
|
2007-04-21 22:08:31 +08:00
|
|
|
printk(IBM_ERR "kzalloc(ibm->driver) failed\n");
|
|
|
|
return -ENOMEM;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
sprintf(ibm->acpi->driver->name, "%s_%s", IBM_NAME, ibm->name);
|
|
|
|
ibm->acpi->driver->ids = ibm->acpi->hid;
|
2007-07-23 20:44:41 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->acpi->driver->ops.add = &tpacpi_device_add;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
rc = acpi_bus_register_driver(ibm->acpi->driver);
|
|
|
|
if (rc < 0) {
|
2007-03-24 04:33:57 +08:00
|
|
|
printk(IBM_ERR "acpi_bus_register_driver(%s) failed: %d\n",
|
2007-07-23 20:44:41 +08:00
|
|
|
ibm->name, rc);
|
2007-04-21 22:08:37 +08:00
|
|
|
kfree(ibm->acpi->driver);
|
|
|
|
ibm->acpi->driver = NULL;
|
2007-04-28 09:00:14 +08:00
|
|
|
} else if (!rc)
|
2007-04-21 22:08:37 +08:00
|
|
|
ibm->flags.acpi_driver_registered = 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-28 09:00:14 +08:00
|
|
|
return rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Procfs Helpers
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int dispatch_procfs_read(char *page, char **start, off_t off,
|
|
|
|
int count, int *eof, void *data)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm = data;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if (!ibm || !ibm->read)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
len = ibm->read(page);
|
|
|
|
if (len < 0)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if (len <= off + count)
|
|
|
|
*eof = 1;
|
|
|
|
*start = page + off;
|
|
|
|
len -= off;
|
|
|
|
if (len > count)
|
|
|
|
len = count;
|
|
|
|
if (len < 0)
|
|
|
|
len = 0;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static int dispatch_procfs_write(struct file *file,
|
|
|
|
const char __user * userbuf,
|
|
|
|
unsigned long count, void *data)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
struct ibm_struct *ibm = data;
|
|
|
|
char *kernbuf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!ibm || !ibm->write)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
kernbuf = kmalloc(count + 2, GFP_KERNEL);
|
|
|
|
if (!kernbuf)
|
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
if (copy_from_user(kernbuf, userbuf, count)) {
|
|
|
|
kfree(kernbuf);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
kernbuf[count] = 0;
|
|
|
|
strcat(kernbuf, ",");
|
|
|
|
ret = ibm->write(kernbuf);
|
|
|
|
if (ret == 0)
|
|
|
|
ret = count;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
kfree(kernbuf);
|
|
|
|
|
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *next_cmd(char **cmds)
|
|
|
|
{
|
|
|
|
char *start = *cmds;
|
|
|
|
char *end;
|
|
|
|
|
|
|
|
while ((end = strchr(start, ',')) && end == start)
|
|
|
|
start = end + 1;
|
|
|
|
|
|
|
|
if (!end)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*end = 0;
|
|
|
|
*cmds = end + 1;
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
2007-07-19 10:45:34 +08:00
|
|
|
* Device model: input, hwmon and platform
|
2007-04-24 22:48:12 +08:00
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct platform_device *tpacpi_pdev;
|
2007-09-25 17:38:03 +08:00
|
|
|
static struct platform_device *tpacpi_sensors_pdev;
|
2007-08-21 04:46:20 +08:00
|
|
|
static struct device *tpacpi_hwmon;
|
2007-07-19 10:45:34 +08:00
|
|
|
static struct input_dev *tpacpi_inputdev;
|
2007-09-23 22:39:01 +08:00
|
|
|
static struct mutex tpacpi_inputdev_send_mutex;
|
2007-04-24 22:48:12 +08:00
|
|
|
|
2007-07-19 10:45:37 +08:00
|
|
|
|
|
|
|
static int tpacpi_resume_handler(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct ibm_struct *ibm, *itmp;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(ibm, itmp,
|
|
|
|
&tpacpi_all_drivers,
|
|
|
|
all_drivers) {
|
|
|
|
if (ibm->resume)
|
|
|
|
(ibm->resume)();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
static struct platform_driver tpacpi_pdriver = {
|
|
|
|
.driver = {
|
|
|
|
.name = IBM_DRVR_NAME,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
2007-07-19 10:45:37 +08:00
|
|
|
.resume = tpacpi_resume_handler,
|
2007-04-24 22:48:12 +08:00
|
|
|
};
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
static struct platform_driver tpacpi_hwmon_pdriver = {
|
|
|
|
.driver = {
|
|
|
|
.name = IBM_HWMON_DRVR_NAME,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
};
|
2007-04-24 22:48:12 +08:00
|
|
|
|
2007-04-24 22:48:13 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* thinkpad-acpi driver attributes
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* interface_version --------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_interface_version_show(
|
|
|
|
struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(interface_version, S_IRUGO,
|
|
|
|
tpacpi_driver_interface_version_show, NULL);
|
|
|
|
|
|
|
|
/* debug_level --------------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
2007-04-24 22:48:14 +08:00
|
|
|
if (parse_strtoul(buf, 0xffff, &t))
|
2007-04-24 22:48:13 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
dbg_level = t;
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
|
|
|
|
tpacpi_driver_debug_show, tpacpi_driver_debug_store);
|
|
|
|
|
|
|
|
/* version ------------------------------------------------------------- */
|
|
|
|
static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s v%s\n", IBM_DESC, IBM_VERSION);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(version, S_IRUGO,
|
|
|
|
tpacpi_driver_version_show, NULL);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static struct driver_attribute* tpacpi_driver_attributes[] = {
|
|
|
|
&driver_attr_debug_level, &driver_attr_version,
|
|
|
|
&driver_attr_interface_version,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
|
|
|
|
{
|
|
|
|
int i, res;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
res = 0;
|
|
|
|
while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
|
|
|
|
res = driver_create_file(drv, tpacpi_driver_attributes[i]);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tpacpi_remove_driver_attributes(struct device_driver *drv)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
|
|
|
|
driver_remove_file(drv, tpacpi_driver_attributes[i]);
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:14 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* sysfs support helpers
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct attribute_set_obj {
|
|
|
|
struct attribute_set s;
|
|
|
|
struct attribute *a;
|
|
|
|
} __attribute__((packed));
|
|
|
|
|
|
|
|
static struct attribute_set *create_attr_set(unsigned int max_members,
|
|
|
|
const char* name)
|
|
|
|
{
|
|
|
|
struct attribute_set_obj *sobj;
|
|
|
|
|
|
|
|
if (max_members == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Allocates space for implicit NULL at the end too */
|
|
|
|
sobj = kzalloc(sizeof(struct attribute_set_obj) +
|
|
|
|
max_members * sizeof(struct attribute *),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!sobj)
|
|
|
|
return NULL;
|
|
|
|
sobj->s.max_members = max_members;
|
|
|
|
sobj->s.group.attrs = &sobj->a;
|
|
|
|
sobj->s.group.name = name;
|
|
|
|
|
|
|
|
return &sobj->s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not multi-threaded safe, use it in a single thread per set */
|
|
|
|
static int add_to_attr_set(struct attribute_set* s, struct attribute *attr)
|
|
|
|
{
|
|
|
|
if (!s || !attr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (s->members >= s->max_members)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
s->group.attrs[s->members] = attr;
|
|
|
|
s->members++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_many_to_attr_set(struct attribute_set* s,
|
|
|
|
struct attribute **attr,
|
|
|
|
unsigned int count)
|
|
|
|
{
|
|
|
|
int i, res;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
res = add_to_attr_set(s, attr[i]);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void delete_attr_set(struct attribute_set* s, struct kobject *kobj)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(kobj, &s->group);
|
|
|
|
destroy_attr_set(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_strtoul(const char *buf,
|
|
|
|
unsigned long max, unsigned long *value)
|
|
|
|
{
|
|
|
|
char *endp;
|
|
|
|
|
2007-10-08 21:12:56 +08:00
|
|
|
while (*buf && isspace(*buf))
|
|
|
|
buf++;
|
2007-04-24 22:48:14 +08:00
|
|
|
*value = simple_strtoul(buf, &endp, 0);
|
|
|
|
while (*endp && isspace(*endp))
|
|
|
|
endp++;
|
|
|
|
if (*endp || *value > max)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Subdrivers
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/*************************************************************************
|
2007-04-21 22:08:26 +08:00
|
|
|
* thinkpad-acpi init subdriver
|
2007-03-24 04:33:57 +08:00
|
|
|
*/
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
printk(IBM_INFO "%s v%s\n", IBM_DESC, IBM_VERSION);
|
|
|
|
printk(IBM_INFO "%s\n", IBM_URL);
|
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
printk(IBM_INFO "ThinkPad BIOS %s, EC %s\n",
|
|
|
|
(thinkpad_id.bios_version_str) ?
|
|
|
|
thinkpad_id.bios_version_str : "unknown",
|
|
|
|
(thinkpad_id.ec_version_str) ?
|
|
|
|
thinkpad_id.ec_version_str : "unknown");
|
|
|
|
|
|
|
|
if (thinkpad_id.vendor && thinkpad_id.model_str)
|
|
|
|
printk(IBM_INFO "%s %s\n",
|
|
|
|
(thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
|
|
|
|
"IBM" : ((thinkpad_id.vendor ==
|
|
|
|
PCI_VENDOR_ID_LENOVO) ?
|
|
|
|
"Lenovo" : "Unknown vendor"),
|
|
|
|
thinkpad_id.model_str);
|
2007-02-07 05:13:44 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-29 12:58:43 +08:00
|
|
|
static int thinkpad_acpi_driver_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
|
|
|
|
len += sprintf(p + len, "driver:\t\t%s\n", IBM_DESC);
|
|
|
|
len += sprintf(p + len, "version:\t%s\n", IBM_VERSION);
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct thinkpad_acpi_driver_data = {
|
|
|
|
.name = "driver",
|
|
|
|
.read = thinkpad_acpi_driver_read,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Hotkey subdriver
|
|
|
|
*/
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
enum { /* Keys available through NVRAM polling */
|
|
|
|
TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U,
|
|
|
|
TPACPI_HKEY_NVRAM_GOOD_MASK = 0x00fb8000U,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* Positions of some of the keys in hotkey masks */
|
|
|
|
TP_ACPI_HKEY_DISPSWTCH_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF7,
|
|
|
|
TP_ACPI_HKEY_DISPXPAND_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF8,
|
|
|
|
TP_ACPI_HKEY_HIBERNATE_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF12,
|
|
|
|
TP_ACPI_HKEY_BRGHTUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNHOME,
|
|
|
|
TP_ACPI_HKEY_BRGHTDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNEND,
|
|
|
|
TP_ACPI_HKEY_THNKLGHT_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP,
|
|
|
|
TP_ACPI_HKEY_ZOOM_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE,
|
|
|
|
TP_ACPI_HKEY_VOLUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP,
|
|
|
|
TP_ACPI_HKEY_VOLDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
|
|
|
|
TP_ACPI_HKEY_MUTE_MASK = 1 << TP_ACPI_HOTKEYSCAN_MUTE,
|
|
|
|
TP_ACPI_HKEY_THINKPAD_MASK = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum { /* NVRAM to ACPI HKEY group map */
|
|
|
|
TP_NVRAM_HKEY_GROUP_HK2 = TP_ACPI_HKEY_THINKPAD_MASK |
|
|
|
|
TP_ACPI_HKEY_ZOOM_MASK |
|
|
|
|
TP_ACPI_HKEY_DISPSWTCH_MASK |
|
|
|
|
TP_ACPI_HKEY_HIBERNATE_MASK,
|
|
|
|
TP_NVRAM_HKEY_GROUP_BRIGHTNESS = TP_ACPI_HKEY_BRGHTUP_MASK |
|
|
|
|
TP_ACPI_HKEY_BRGHTDWN_MASK,
|
|
|
|
TP_NVRAM_HKEY_GROUP_VOLUME = TP_ACPI_HKEY_VOLUP_MASK |
|
|
|
|
TP_ACPI_HKEY_VOLDWN_MASK |
|
|
|
|
TP_ACPI_HKEY_MUTE_MASK,
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
struct tp_nvram_state {
|
|
|
|
u16 thinkpad_toggle:1;
|
|
|
|
u16 zoom_toggle:1;
|
|
|
|
u16 display_toggle:1;
|
|
|
|
u16 thinklight_toggle:1;
|
|
|
|
u16 hibernate_toggle:1;
|
|
|
|
u16 displayexp_toggle:1;
|
|
|
|
u16 display_state:1;
|
|
|
|
u16 brightness_toggle:1;
|
|
|
|
u16 volume_toggle:1;
|
|
|
|
u16 mute:1;
|
|
|
|
|
|
|
|
u8 brightness_level;
|
|
|
|
u8 volume_level;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct task_struct *tpacpi_hotkey_task;
|
|
|
|
static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */
|
|
|
|
static int hotkey_poll_freq = 10; /* Hz */
|
|
|
|
static struct mutex hotkey_thread_mutex;
|
|
|
|
static struct mutex hotkey_thread_data_mutex;
|
|
|
|
static unsigned int hotkey_config_change;
|
|
|
|
|
|
|
|
#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
|
|
|
#define hotkey_source_mask 0U
|
|
|
|
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int hotkey_orig_status;
|
2007-07-19 10:45:29 +08:00
|
|
|
static u32 hotkey_orig_mask;
|
2007-07-19 10:45:30 +08:00
|
|
|
static u32 hotkey_all_mask;
|
2007-07-19 10:45:35 +08:00
|
|
|
static u32 hotkey_reserved_mask;
|
2008-01-08 23:02:39 +08:00
|
|
|
static u32 hotkey_mask;
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
static u16 *hotkey_keycode_map;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct attribute_set *hotkey_dev_attributes;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
#define HOTKEY_CONFIG_CRITICAL_START \
|
|
|
|
mutex_lock(&hotkey_thread_data_mutex); \
|
|
|
|
hotkey_config_change++;
|
|
|
|
#define HOTKEY_CONFIG_CRITICAL_END \
|
|
|
|
mutex_unlock(&hotkey_thread_data_mutex);
|
|
|
|
#else
|
|
|
|
#define HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
#define HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2007-07-19 10:45:31 +08:00
|
|
|
static int hotkey_get_wlsw(int *status)
|
|
|
|
{
|
|
|
|
if (!acpi_evalf(hkey_handle, status, "WLSW", "d"))
|
|
|
|
return -EIO;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
/*
|
|
|
|
* Call with hotkey_mutex held
|
|
|
|
*/
|
|
|
|
static int hotkey_mask_get(void)
|
|
|
|
{
|
2008-01-08 23:02:41 +08:00
|
|
|
u32 m = 0;
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
if (tp_features.hotkey_mask) {
|
2008-01-08 23:02:41 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
|
2008-01-08 23:02:39 +08:00
|
|
|
return -EIO;
|
|
|
|
}
|
2008-01-08 23:02:41 +08:00
|
|
|
hotkey_mask = m | (hotkey_source_mask & hotkey_mask);
|
2008-01-08 23:02:39 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Call with hotkey_mutex held
|
|
|
|
*/
|
|
|
|
static int hotkey_mask_set(u32 mask)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (tp_features.hotkey_mask) {
|
2008-01-08 23:02:41 +08:00
|
|
|
HOTKEY_CONFIG_CRITICAL_START
|
2008-01-08 23:02:39 +08:00
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
u32 m = 1 << i;
|
2008-01-08 23:02:41 +08:00
|
|
|
/* enable in firmware mask only keys not in NVRAM
|
|
|
|
* mode, but enable the key in the cached hotkey_mask
|
|
|
|
* regardless of mode, or the key will end up
|
|
|
|
* disabled by hotkey_mask_get() */
|
2008-01-08 23:02:39 +08:00
|
|
|
if (!acpi_evalf(hkey_handle,
|
|
|
|
NULL, "MHKM", "vdd", i + 1,
|
2008-01-08 23:02:41 +08:00
|
|
|
!!((mask & ~hotkey_source_mask) & m))) {
|
2008-01-08 23:02:39 +08:00
|
|
|
rc = -EIO;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
hotkey_mask = (hotkey_mask & ~m) | (mask & m);
|
|
|
|
}
|
|
|
|
}
|
2008-01-08 23:02:41 +08:00
|
|
|
HOTKEY_CONFIG_CRITICAL_END
|
2008-01-08 23:02:39 +08:00
|
|
|
|
|
|
|
/* hotkey_mask_get must be called unconditionally below */
|
2008-01-08 23:02:41 +08:00
|
|
|
if (!hotkey_mask_get() && !rc &&
|
|
|
|
(hotkey_mask & ~hotkey_source_mask) !=
|
|
|
|
(mask & ~hotkey_source_mask)) {
|
2008-01-08 23:02:39 +08:00
|
|
|
printk(IBM_NOTICE
|
|
|
|
"requested hot key mask 0x%08x, but "
|
|
|
|
"firmware forced it to 0x%08x\n",
|
|
|
|
mask, hotkey_mask);
|
|
|
|
}
|
2008-01-08 23:02:41 +08:00
|
|
|
} else {
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
hotkey_mask = mask & hotkey_source_mask;
|
|
|
|
HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
hotkey_mask_get();
|
|
|
|
if (hotkey_mask != mask) {
|
|
|
|
printk(IBM_NOTICE
|
|
|
|
"requested hot key mask 0x%08x, "
|
|
|
|
"forced to 0x%08x (NVRAM poll mask is "
|
|
|
|
"0x%08x): no firmware mask support\n",
|
|
|
|
mask, hotkey_mask, hotkey_source_mask);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
hotkey_mask_get();
|
|
|
|
rc = -ENXIO;
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
2008-01-08 23:02:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hotkey_status_get(int *status)
|
|
|
|
{
|
|
|
|
if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hotkey_status_set(int status)
|
|
|
|
{
|
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:40 +08:00
|
|
|
static void tpacpi_input_send_radiosw(void)
|
|
|
|
{
|
|
|
|
int wlsw;
|
|
|
|
|
|
|
|
mutex_lock(&tpacpi_inputdev_send_mutex);
|
|
|
|
|
|
|
|
if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) {
|
|
|
|
input_report_switch(tpacpi_inputdev,
|
|
|
|
SW_RADIO, !!wlsw);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&tpacpi_inputdev_send_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tpacpi_input_send_key(unsigned int scancode)
|
|
|
|
{
|
|
|
|
unsigned int keycode;
|
|
|
|
|
|
|
|
keycode = hotkey_keycode_map[scancode];
|
|
|
|
|
|
|
|
if (keycode != KEY_RESERVED) {
|
|
|
|
mutex_lock(&tpacpi_inputdev_send_mutex);
|
|
|
|
|
|
|
|
input_report_key(tpacpi_inputdev, keycode, 1);
|
|
|
|
if (keycode == KEY_UNKNOWN)
|
|
|
|
input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
|
|
|
|
scancode);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
|
|
|
input_report_key(tpacpi_inputdev, keycode, 0);
|
|
|
|
if (keycode == KEY_UNKNOWN)
|
|
|
|
input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN,
|
|
|
|
scancode);
|
|
|
|
input_sync(tpacpi_inputdev);
|
|
|
|
|
|
|
|
mutex_unlock(&tpacpi_inputdev_send_mutex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
|
|
|
|
|
|
|
|
static void tpacpi_hotkey_send_key(unsigned int scancode)
|
|
|
|
{
|
|
|
|
tpacpi_input_send_key(scancode);
|
|
|
|
if (hotkey_report_mode < 2) {
|
|
|
|
acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
|
|
|
|
0x80, 0x1001 + scancode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_read_nvram(struct tp_nvram_state *n, u32 m)
|
|
|
|
{
|
|
|
|
u8 d;
|
|
|
|
|
|
|
|
if (m & TP_NVRAM_HKEY_GROUP_HK2) {
|
|
|
|
d = nvram_read_byte(TP_NVRAM_ADDR_HK2);
|
|
|
|
n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD);
|
|
|
|
n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM);
|
|
|
|
n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY);
|
|
|
|
n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE);
|
|
|
|
}
|
|
|
|
if (m & TP_ACPI_HKEY_THNKLGHT_MASK) {
|
|
|
|
d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
|
|
|
|
n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT);
|
|
|
|
}
|
|
|
|
if (m & TP_ACPI_HKEY_DISPXPAND_MASK) {
|
|
|
|
d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO);
|
|
|
|
n->displayexp_toggle =
|
|
|
|
!!(d & TP_NVRAM_MASK_HKT_DISPEXPND);
|
|
|
|
}
|
|
|
|
if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) {
|
|
|
|
d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
|
|
|
|
n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
|
|
|
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
|
|
|
|
n->brightness_toggle =
|
|
|
|
!!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS);
|
|
|
|
}
|
|
|
|
if (m & TP_NVRAM_HKEY_GROUP_VOLUME) {
|
|
|
|
d = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
|
|
|
|
n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME)
|
|
|
|
>> TP_NVRAM_POS_LEVEL_VOLUME;
|
|
|
|
n->mute = !!(d & TP_NVRAM_MASK_MUTE);
|
|
|
|
n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define TPACPI_COMPARE_KEY(__scancode, __member) \
|
|
|
|
do { if ((mask & (1 << __scancode)) && oldn->__member != newn->__member) \
|
|
|
|
tpacpi_hotkey_send_key(__scancode); } while (0)
|
|
|
|
|
|
|
|
#define TPACPI_MAY_SEND_KEY(__scancode) \
|
|
|
|
do { if (mask & (1 << __scancode)) \
|
|
|
|
tpacpi_hotkey_send_key(__scancode); } while (0)
|
|
|
|
|
|
|
|
static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
|
|
|
|
struct tp_nvram_state *newn,
|
|
|
|
u32 mask)
|
|
|
|
{
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle);
|
|
|
|
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle);
|
|
|
|
|
|
|
|
TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
|
|
|
|
|
|
|
|
/* handle volume */
|
|
|
|
if (oldn->volume_toggle != newn->volume_toggle) {
|
|
|
|
if (oldn->mute != newn->mute) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
|
|
|
|
}
|
|
|
|
if (oldn->volume_level > newn->volume_level) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
|
|
|
} else if (oldn->volume_level < newn->volume_level) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
|
|
|
} else if (oldn->mute == newn->mute) {
|
|
|
|
/* repeated key presses that didn't change state */
|
|
|
|
if (newn->mute) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
|
|
|
|
} else if (newn->volume_level != 0) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
|
|
|
|
} else {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* handle brightness */
|
|
|
|
if (oldn->brightness_toggle != newn->brightness_toggle) {
|
|
|
|
if (oldn->brightness_level < newn->brightness_level) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
|
|
|
} else if (oldn->brightness_level > newn->brightness_level) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
|
|
|
} else {
|
|
|
|
/* repeated key presses that didn't change state */
|
|
|
|
if (newn->brightness_level != 0) {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
|
|
|
|
} else {
|
|
|
|
TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef TPACPI_COMPARE_KEY
|
|
|
|
#undef TPACPI_MAY_SEND_KEY
|
|
|
|
|
|
|
|
static int hotkey_kthread(void *data)
|
|
|
|
{
|
|
|
|
struct tp_nvram_state s[2];
|
|
|
|
u32 mask;
|
|
|
|
unsigned int si, so;
|
|
|
|
unsigned long t;
|
|
|
|
unsigned int change_detector, must_reset;
|
|
|
|
|
|
|
|
mutex_lock(&hotkey_thread_mutex);
|
|
|
|
|
|
|
|
if (tpacpi_lifecycle == TPACPI_LIFE_EXITING)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
set_freezable();
|
|
|
|
|
|
|
|
so = 0;
|
|
|
|
si = 1;
|
|
|
|
t = 0;
|
|
|
|
|
|
|
|
/* Initial state for compares */
|
|
|
|
mutex_lock(&hotkey_thread_data_mutex);
|
|
|
|
change_detector = hotkey_config_change;
|
|
|
|
mask = hotkey_source_mask & hotkey_mask;
|
|
|
|
mutex_unlock(&hotkey_thread_data_mutex);
|
|
|
|
hotkey_read_nvram(&s[so], mask);
|
|
|
|
|
|
|
|
while (!kthread_should_stop() && hotkey_poll_freq) {
|
|
|
|
if (t == 0)
|
|
|
|
t = 1000/hotkey_poll_freq;
|
|
|
|
t = msleep_interruptible(t);
|
|
|
|
if (unlikely(kthread_should_stop()))
|
|
|
|
break;
|
|
|
|
must_reset = try_to_freeze();
|
|
|
|
if (t > 0 && !must_reset)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
mutex_lock(&hotkey_thread_data_mutex);
|
|
|
|
if (must_reset || hotkey_config_change != change_detector) {
|
|
|
|
/* forget old state on thaw or config change */
|
|
|
|
si = so;
|
|
|
|
t = 0;
|
|
|
|
change_detector = hotkey_config_change;
|
|
|
|
}
|
|
|
|
mask = hotkey_source_mask & hotkey_mask;
|
|
|
|
mutex_unlock(&hotkey_thread_data_mutex);
|
|
|
|
|
|
|
|
if (likely(mask)) {
|
|
|
|
hotkey_read_nvram(&s[si], mask);
|
|
|
|
if (likely(si != so)) {
|
|
|
|
hotkey_compare_and_issue_event(&s[so], &s[si],
|
|
|
|
mask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
so = si;
|
|
|
|
si ^= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
mutex_unlock(&hotkey_thread_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_poll_stop_sync(void)
|
|
|
|
{
|
|
|
|
if (tpacpi_hotkey_task) {
|
|
|
|
if (frozen(tpacpi_hotkey_task) ||
|
|
|
|
freezing(tpacpi_hotkey_task))
|
|
|
|
thaw_process(tpacpi_hotkey_task);
|
|
|
|
|
|
|
|
kthread_stop(tpacpi_hotkey_task);
|
|
|
|
tpacpi_hotkey_task = NULL;
|
|
|
|
mutex_lock(&hotkey_thread_mutex);
|
|
|
|
/* at this point, the thread did exit */
|
|
|
|
mutex_unlock(&hotkey_thread_mutex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* call with hotkey_mutex held */
|
|
|
|
static void hotkey_poll_setup(int may_warn)
|
|
|
|
{
|
|
|
|
if ((hotkey_source_mask & hotkey_mask) != 0 &&
|
|
|
|
hotkey_poll_freq > 0 &&
|
|
|
|
(tpacpi_inputdev->users > 0 || hotkey_report_mode < 2)) {
|
|
|
|
if (!tpacpi_hotkey_task) {
|
|
|
|
tpacpi_hotkey_task = kthread_run(hotkey_kthread,
|
|
|
|
NULL, IBM_FILE "d");
|
|
|
|
if (IS_ERR(tpacpi_hotkey_task)) {
|
|
|
|
tpacpi_hotkey_task = NULL;
|
|
|
|
printk(IBM_ERR "could not create kernel thread "
|
|
|
|
"for hotkey polling\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hotkey_poll_stop_sync();
|
|
|
|
if (may_warn &&
|
|
|
|
hotkey_source_mask != 0 && hotkey_poll_freq == 0) {
|
|
|
|
printk(IBM_NOTICE "hot keys 0x%08x require polling, "
|
|
|
|
"which is currently disabled\n",
|
|
|
|
hotkey_source_mask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_poll_setup_safe(int may_warn)
|
|
|
|
{
|
|
|
|
mutex_lock(&hotkey_mutex);
|
|
|
|
hotkey_poll_setup(may_warn);
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hotkey_inputdev_open(struct input_dev *dev)
|
|
|
|
{
|
|
|
|
switch (tpacpi_lifecycle) {
|
|
|
|
case TPACPI_LIFE_INIT:
|
|
|
|
/*
|
|
|
|
* hotkey_init will call hotkey_poll_setup_safe
|
|
|
|
* at the appropriate moment
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
case TPACPI_LIFE_EXITING:
|
|
|
|
return -EBUSY;
|
|
|
|
case TPACPI_LIFE_RUNNING:
|
|
|
|
hotkey_poll_setup_safe(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Should only happen if tpacpi_lifecycle is corrupt */
|
|
|
|
BUG();
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_inputdev_close(struct input_dev *dev)
|
|
|
|
{
|
|
|
|
/* disable hotkey polling when possible */
|
|
|
|
if (tpacpi_lifecycle == TPACPI_LIFE_RUNNING)
|
|
|
|
hotkey_poll_setup_safe(0);
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* sysfs hotkey enable ------------------------------------------------- */
|
|
|
|
static ssize_t hotkey_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
int res, status;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_get(&status);
|
2007-04-28 09:00:16 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", status);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
2008-01-08 23:02:39 +08:00
|
|
|
int res;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_set(t);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
return (res) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:16 +08:00
|
|
|
hotkey_enable_show, hotkey_enable_store);
|
|
|
|
|
|
|
|
/* sysfs hotkey mask --------------------------------------------------- */
|
|
|
|
static ssize_t hotkey_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2008-01-08 23:02:39 +08:00
|
|
|
int res;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
res = hotkey_mask_get();
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
return (res)?
|
|
|
|
res : snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_mask);
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_mask_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
2008-01-08 23:02:39 +08:00
|
|
|
int res;
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2007-07-19 10:45:29 +08:00
|
|
|
if (parse_strtoul(buf, 0xffffffffUL, &t))
|
2007-04-28 09:00:16 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
res = hotkey_mask_set(t);
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
hotkey_poll_setup(1);
|
|
|
|
#endif
|
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
return (res) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_mask =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_mask, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:16 +08:00
|
|
|
hotkey_mask_show, hotkey_mask_store);
|
|
|
|
|
|
|
|
/* sysfs hotkey bios_enabled ------------------------------------------- */
|
|
|
|
static ssize_t hotkey_bios_enabled_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_orig_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_bios_enabled =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
/* sysfs hotkey bios_mask ---------------------------------------------- */
|
|
|
|
static ssize_t hotkey_bios_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_bios_mask =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
|
2007-04-28 09:00:16 +08:00
|
|
|
|
2007-07-19 10:45:30 +08:00
|
|
|
/* sysfs hotkey all_mask ----------------------------------------------- */
|
|
|
|
static ssize_t hotkey_all_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
2008-01-08 23:02:41 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n",
|
|
|
|
hotkey_all_mask | hotkey_source_mask);
|
2007-07-19 10:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_all_mask =
|
|
|
|
__ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL);
|
|
|
|
|
|
|
|
/* sysfs hotkey recommended_mask --------------------------------------- */
|
|
|
|
static ssize_t hotkey_recommended_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n",
|
2008-01-08 23:02:41 +08:00
|
|
|
(hotkey_all_mask | hotkey_source_mask)
|
|
|
|
& ~hotkey_reserved_mask);
|
2007-07-19 10:45:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_recommended_mask =
|
|
|
|
__ATTR(hotkey_recommended_mask, S_IRUGO,
|
|
|
|
hotkey_recommended_mask_show, NULL);
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
|
|
|
|
/* sysfs hotkey hotkey_source_mask ------------------------------------- */
|
|
|
|
static ssize_t hotkey_source_mask_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_source_mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_source_mask_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 0xffffffffUL, &t) ||
|
|
|
|
((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
HOTKEY_CONFIG_CRITICAL_START
|
|
|
|
hotkey_source_mask = t;
|
|
|
|
HOTKEY_CONFIG_CRITICAL_END
|
|
|
|
|
|
|
|
hotkey_poll_setup(1);
|
|
|
|
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_source_mask =
|
|
|
|
__ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO,
|
|
|
|
hotkey_source_mask_show, hotkey_source_mask_store);
|
|
|
|
|
|
|
|
/* sysfs hotkey hotkey_poll_freq --------------------------------------- */
|
|
|
|
static ssize_t hotkey_poll_freq_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_poll_freq);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hotkey_poll_freq_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 25, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
hotkey_poll_freq = t;
|
|
|
|
|
|
|
|
hotkey_poll_setup(1);
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_poll_freq =
|
|
|
|
__ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO,
|
|
|
|
hotkey_poll_freq_show, hotkey_poll_freq_store);
|
|
|
|
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
|
|
|
|
|
2007-07-19 10:45:31 +08:00
|
|
|
/* sysfs hotkey radio_sw ----------------------------------------------- */
|
|
|
|
static ssize_t hotkey_radio_sw_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res, s;
|
|
|
|
res = hotkey_get_wlsw(&s);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_radio_sw =
|
|
|
|
__ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL);
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* sysfs hotkey report_mode -------------------------------------------- */
|
|
|
|
static ssize_t hotkey_report_mode_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",
|
|
|
|
(hotkey_report_mode != 0) ? hotkey_report_mode : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_hotkey_report_mode =
|
|
|
|
__ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL);
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
static struct attribute *hotkey_attributes[] __initdata = {
|
|
|
|
&dev_attr_hotkey_enable.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
&dev_attr_hotkey_bios_enabled.attr,
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
&dev_attr_hotkey_report_mode.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
&dev_attr_hotkey_mask.attr,
|
|
|
|
&dev_attr_hotkey_all_mask.attr,
|
|
|
|
&dev_attr_hotkey_recommended_mask.attr,
|
|
|
|
&dev_attr_hotkey_source_mask.attr,
|
|
|
|
&dev_attr_hotkey_poll_freq.attr,
|
|
|
|
#endif
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute *hotkey_mask_attributes[] __initdata = {
|
2007-04-28 09:00:16 +08:00
|
|
|
&dev_attr_hotkey_bios_mask.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifndef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
&dev_attr_hotkey_mask.attr,
|
2007-07-19 10:45:30 +08:00
|
|
|
&dev_attr_hotkey_all_mask.attr,
|
|
|
|
&dev_attr_hotkey_recommended_mask.attr,
|
2008-01-08 23:02:41 +08:00
|
|
|
#endif
|
2007-04-28 09:00:16 +08:00
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init hotkey_init(struct ibm_init_struct *iibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
/* Requirements for changing the default keymaps:
|
|
|
|
*
|
|
|
|
* 1. Many of the keys are mapped to KEY_RESERVED for very
|
|
|
|
* good reasons. Do not change them unless you have deep
|
|
|
|
* knowledge on the IBM and Lenovo ThinkPad firmware for
|
|
|
|
* the various ThinkPad models. The driver behaves
|
|
|
|
* differently for KEY_RESERVED: such keys have their
|
|
|
|
* hot key mask *unset* in mask_recommended, and also
|
|
|
|
* in the initial hot key mask programmed into the
|
|
|
|
* firmware at driver load time, which means the firm-
|
|
|
|
* ware may react very differently if you change them to
|
|
|
|
* something else;
|
|
|
|
*
|
|
|
|
* 2. You must be subscribed to the linux-thinkpad and
|
|
|
|
* ibm-acpi-devel mailing lists, and you should read the
|
|
|
|
* list archives since 2007 if you want to change the
|
|
|
|
* keymaps. This requirement exists so that you will
|
|
|
|
* know the past history of problems with the thinkpad-
|
|
|
|
* acpi driver keymaps, and also that you will be
|
|
|
|
* listening to any bug reports;
|
|
|
|
*
|
|
|
|
* 3. Do not send thinkpad-acpi specific patches directly to
|
|
|
|
* for merging, *ever*. Send them to the linux-acpi
|
|
|
|
* mailinglist for comments. Merging is to be done only
|
|
|
|
* through acpi-test and the ACPI maintainer.
|
|
|
|
*
|
|
|
|
* If the above is too much to ask, don't change the keymap.
|
|
|
|
* Ask the thinkpad-acpi maintainer to do it, instead.
|
|
|
|
*/
|
2007-07-19 10:45:44 +08:00
|
|
|
static u16 ibm_keycode_map[] __initdata = {
|
|
|
|
/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
|
|
|
|
KEY_FN_F1, KEY_FN_F2, KEY_COFFEE, KEY_SLEEP,
|
|
|
|
KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
|
|
|
|
KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
|
|
|
|
KEY_UNKNOWN, /* 0x0D: FN+INSERT */
|
|
|
|
KEY_UNKNOWN, /* 0x0E: FN+DELETE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* brightness: firmware always reacts to them, unless
|
|
|
|
* X.org did some tricks in the radeon BIOS scratch
|
|
|
|
* registers of *some* models */
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
|
|
|
|
KEY_RESERVED, /* 0x10: FN+END (brightness down) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Thinklight: firmware always react to it */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
|
|
|
|
KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Volume: firmware always react to it and reprograms
|
|
|
|
* the built-in *extra* mixer. Never map it to control
|
|
|
|
* another mixer by default. */
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x14: VOLUME UP */
|
|
|
|
KEY_RESERVED, /* 0x15: VOLUME DOWN */
|
|
|
|
KEY_RESERVED, /* 0x16: MUTE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
/* (assignments unknown, please report if found) */
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
};
|
|
|
|
static u16 lenovo_keycode_map[] __initdata = {
|
|
|
|
/* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
|
|
|
|
KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP,
|
|
|
|
KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
|
|
|
|
KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
|
|
|
|
KEY_UNKNOWN, /* 0x0D: FN+INSERT */
|
|
|
|
KEY_UNKNOWN, /* 0x0E: FN+DELETE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-12-13 22:14:09 +08:00
|
|
|
KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
|
|
|
|
KEY_RESERVED, /* 0x10: FN+END (brightness down) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
|
|
|
|
KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
|
|
|
/* Volume: z60/z61, T60 (BIOS version?): firmware always
|
|
|
|
* react to it and reprograms the built-in *extra* mixer.
|
|
|
|
* Never map it to control another mixer by default.
|
|
|
|
*
|
|
|
|
* T60?, T61, R60?, R61: firmware and EC tries to send
|
|
|
|
* these over the regular keyboard, so these are no-ops,
|
|
|
|
* but there are still weird bugs re. MUTE, so do not
|
|
|
|
* change unless you get test reports from all Lenovo
|
|
|
|
* models. May cause the BIOS to interfere with the
|
|
|
|
* HDA mixer.
|
|
|
|
*/
|
2007-10-31 03:46:19 +08:00
|
|
|
KEY_RESERVED, /* 0x14: VOLUME UP */
|
|
|
|
KEY_RESERVED, /* 0x15: VOLUME DOWN */
|
|
|
|
KEY_RESERVED, /* 0x16: MUTE */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
|
ACPI: thinkpad-acpi: document keymap gotcha's (v2)
Publish the requirements for keymap changes. This is a documentation
change, only.
Currently, people look at the thinkpad-acpi default keymaps, and think:
"modifying this is a trivial thing, it can't break systems, and there are
keys defined for foo and bar, but the driver has them as KEY_RESERVED.
Must have been an oversight, let me change it."
And since they never get to see the bug reports, because they are not
really a part of the Linux ThinkPad users community (linux-thinkpad
mailinglist, thinkwiki wiki, thinkpad forums) and laptop users are slow
to complain to distros about any breakages...
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
2008-01-08 23:02:38 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
/* (assignments unknown, please report if found) */
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define TPACPI_HOTKEY_MAP_LEN ARRAY_SIZE(ibm_keycode_map)
|
|
|
|
#define TPACPI_HOTKEY_MAP_SIZE sizeof(ibm_keycode_map)
|
|
|
|
#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(ibm_keycode_map[0])
|
|
|
|
|
2007-07-19 10:45:35 +08:00
|
|
|
int res, i;
|
2007-07-19 10:45:31 +08:00
|
|
|
int status;
|
2007-09-23 22:39:03 +08:00
|
|
|
int hkeyv;
|
2007-04-21 22:08:39 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing hotkey subdriver\n");
|
|
|
|
|
2007-07-19 10:45:35 +08:00
|
|
|
BUG_ON(!tpacpi_inputdev);
|
2008-01-08 23:02:41 +08:00
|
|
|
BUG_ON(tpacpi_inputdev->open != NULL ||
|
|
|
|
tpacpi_inputdev->close != NULL);
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(hkey);
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_init(&hotkey_mutex);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
mutex_init(&hotkey_thread_mutex);
|
|
|
|
mutex_init(&hotkey_thread_data_mutex);
|
|
|
|
#endif
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* hotkey not supported on 570 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.hotkey = hkey_handle != NULL;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "hotkeys are %s\n",
|
2007-04-21 22:08:36 +08:00
|
|
|
str_supported(tp_features.hotkey));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.hotkey) {
|
2008-01-08 23:02:41 +08:00
|
|
|
hotkey_dev_attributes = create_attr_set(10, NULL);
|
2007-04-28 09:00:16 +08:00
|
|
|
if (!hotkey_dev_attributes)
|
|
|
|
return -ENOMEM;
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
res = add_many_to_attr_set(hotkey_dev_attributes,
|
|
|
|
hotkey_attributes,
|
|
|
|
ARRAY_SIZE(hotkey_attributes));
|
2007-04-28 09:00:16 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* mask not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
|
2007-09-23 22:39:03 +08:00
|
|
|
A30, R30, R31, T20-22, X20-21, X22-24. Detected by checking
|
|
|
|
for HKEY interface version 0x100 */
|
|
|
|
if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
|
|
|
|
if ((hkeyv >> 8) != 1) {
|
|
|
|
printk(IBM_ERR "unknown version of the "
|
|
|
|
"HKEY interface: 0x%x\n", hkeyv);
|
|
|
|
printk(IBM_ERR "please report this to %s\n",
|
|
|
|
IBM_MAIL);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* MHKV 0x100 in A31, R40, R40e,
|
|
|
|
* T4x, X31, and later
|
2008-01-08 23:02:41 +08:00
|
|
|
*/
|
2007-09-23 22:39:03 +08:00
|
|
|
tp_features.hotkey_mask = 1;
|
|
|
|
}
|
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "hotkey masks are %s\n",
|
2007-04-21 22:08:36 +08:00
|
|
|
str_supported(tp_features.hotkey_mask));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-07-19 10:45:30 +08:00
|
|
|
if (tp_features.hotkey_mask) {
|
|
|
|
if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
|
2007-09-23 22:39:03 +08:00
|
|
|
"MHKA", "qd")) {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"missing MHKA handler, "
|
|
|
|
"please report this to %s\n",
|
|
|
|
IBM_MAIL);
|
2007-07-19 10:45:30 +08:00
|
|
|
hotkey_all_mask = 0x080cU; /* FN+F12, FN+F4, FN+F3 */
|
2007-09-23 22:39:03 +08:00
|
|
|
}
|
2007-07-19 10:45:30 +08:00
|
|
|
}
|
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
/* hotkey_source_mask *must* be zero for
|
|
|
|
* the first hotkey_mask_get */
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_get(&hotkey_orig_status);
|
2007-04-28 09:00:16 +08:00
|
|
|
if (!res && tp_features.hotkey_mask) {
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_mask_get();
|
|
|
|
hotkey_orig_mask = hotkey_mask;
|
|
|
|
if (!res) {
|
|
|
|
res = add_many_to_attr_set(
|
|
|
|
hotkey_dev_attributes,
|
|
|
|
hotkey_mask_attributes,
|
|
|
|
ARRAY_SIZE(hotkey_mask_attributes));
|
|
|
|
}
|
2007-04-28 09:00:16 +08:00
|
|
|
}
|
2007-07-19 10:45:31 +08:00
|
|
|
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
if (tp_features.hotkey_mask) {
|
|
|
|
hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
|
|
|
|
& ~hotkey_all_mask;
|
|
|
|
} else {
|
|
|
|
hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"hotkey source mask 0x%08x, polling freq %d\n",
|
|
|
|
hotkey_source_mask, hotkey_poll_freq);
|
|
|
|
#endif
|
|
|
|
|
2007-07-19 10:45:31 +08:00
|
|
|
/* Not all thinkpads have a hardware radio switch */
|
|
|
|
if (!res && acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
|
|
|
|
tp_features.hotkey_wlsw = 1;
|
|
|
|
printk(IBM_INFO
|
|
|
|
"radio switch found; radios are %s\n",
|
|
|
|
enabled(status, 0));
|
|
|
|
res = add_to_attr_set(hotkey_dev_attributes,
|
|
|
|
&dev_attr_hotkey_radio_sw.attr);
|
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
if (!res)
|
|
|
|
res = register_attr_set_with_sysfs(
|
|
|
|
hotkey_dev_attributes,
|
|
|
|
&tpacpi_pdev->dev.kobj);
|
2007-04-21 22:08:39 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
2007-07-19 10:45:35 +08:00
|
|
|
|
2007-07-19 10:45:44 +08:00
|
|
|
/* Set up key map */
|
|
|
|
|
|
|
|
hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!hotkey_keycode_map) {
|
|
|
|
printk(IBM_ERR "failed to allocate memory for key map\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO) {
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"using Lenovo default hot key map\n");
|
|
|
|
memcpy(hotkey_keycode_map, &lenovo_keycode_map,
|
|
|
|
TPACPI_HOTKEY_MAP_SIZE);
|
|
|
|
} else {
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"using IBM default hot key map\n");
|
|
|
|
memcpy(hotkey_keycode_map, &ibm_keycode_map,
|
|
|
|
TPACPI_HOTKEY_MAP_SIZE);
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:35 +08:00
|
|
|
set_bit(EV_KEY, tpacpi_inputdev->evbit);
|
|
|
|
set_bit(EV_MSC, tpacpi_inputdev->evbit);
|
|
|
|
set_bit(MSC_SCAN, tpacpi_inputdev->mscbit);
|
2007-07-19 10:45:44 +08:00
|
|
|
tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
|
|
|
|
tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
|
|
|
|
tpacpi_inputdev->keycode = hotkey_keycode_map;
|
|
|
|
for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
|
2007-07-19 10:45:35 +08:00
|
|
|
if (hotkey_keycode_map[i] != KEY_RESERVED) {
|
|
|
|
set_bit(hotkey_keycode_map[i],
|
|
|
|
tpacpi_inputdev->keybit);
|
|
|
|
} else {
|
|
|
|
if (i < sizeof(hotkey_reserved_mask)*8)
|
|
|
|
hotkey_reserved_mask |= 1 << i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:38 +08:00
|
|
|
if (tp_features.hotkey_wlsw) {
|
|
|
|
set_bit(EV_SW, tpacpi_inputdev->evbit);
|
|
|
|
set_bit(SW_RADIO, tpacpi_inputdev->swbit);
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:36 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"enabling hot key handling\n");
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2008-01-08 23:02:41 +08:00
|
|
|
res = hotkey_mask_set(((hotkey_all_mask | hotkey_source_mask)
|
|
|
|
& ~hotkey_reserved_mask)
|
2007-07-19 10:45:36 +08:00
|
|
|
| hotkey_orig_mask);
|
2008-01-08 23:02:41 +08:00
|
|
|
if (res < 0 && res != -ENXIO)
|
2007-07-19 10:45:36 +08:00
|
|
|
return res;
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"legacy hot key reporting over procfs %s\n",
|
|
|
|
(hotkey_report_mode < 2) ?
|
|
|
|
"enabled" : "disabled");
|
2008-01-08 23:02:41 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
tpacpi_inputdev->open = &hotkey_inputdev_open;
|
|
|
|
tpacpi_inputdev->close = &hotkey_inputdev_close;
|
|
|
|
|
|
|
|
hotkey_poll_setup_safe(1);
|
|
|
|
#endif
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
return (tp_features.hotkey)? 0 : 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_exit(void)
|
|
|
|
{
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
hotkey_poll_stop_sync();
|
|
|
|
#endif
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.hotkey) {
|
2008-01-08 23:02:39 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT, "restoring original hot key mask\n");
|
|
|
|
/* no short-circuit boolean operator below! */
|
|
|
|
if ((hotkey_mask_set(hotkey_orig_mask) |
|
|
|
|
hotkey_status_set(hotkey_orig_status)) != 0)
|
|
|
|
printk(IBM_ERR "failed to restore hot key mask to BIOS defaults\n");
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
2007-04-28 09:00:16 +08:00
|
|
|
|
|
|
|
if (hotkey_dev_attributes) {
|
|
|
|
delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
|
|
|
|
hotkey_dev_attributes = NULL;
|
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void hotkey_notify(struct ibm_struct *ibm, u32 event)
|
|
|
|
{
|
2007-07-19 10:45:35 +08:00
|
|
|
u32 hkey;
|
2008-01-08 23:02:40 +08:00
|
|
|
unsigned int scancode;
|
2007-09-23 22:39:04 +08:00
|
|
|
int send_acpi_ev;
|
2007-09-23 22:39:05 +08:00
|
|
|
int ignore_acpi_ev;
|
2007-09-23 22:39:04 +08:00
|
|
|
|
|
|
|
if (event != 0x80) {
|
|
|
|
printk(IBM_ERR "unknown HKEY notification event %d\n", event);
|
|
|
|
/* forward it to userspace, maybe it knows how to handle it */
|
|
|
|
acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
|
|
|
|
ibm->acpi->device->dev.bus_id,
|
|
|
|
event, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
|
|
|
|
printk(IBM_ERR "failed to retrieve HKEY event\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hkey == 0) {
|
|
|
|
/* queue empty */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
send_acpi_ev = 0;
|
2007-09-23 22:39:05 +08:00
|
|
|
ignore_acpi_ev = 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
switch (hkey >> 12) {
|
|
|
|
case 1:
|
|
|
|
/* 0x1000-0x1FFF: key presses */
|
|
|
|
scancode = hkey & 0xfff;
|
|
|
|
if (scancode > 0 && scancode < 0x21) {
|
|
|
|
scancode--;
|
2008-01-08 23:02:41 +08:00
|
|
|
if (!(hotkey_source_mask & (1 << scancode))) {
|
|
|
|
tpacpi_input_send_key(scancode);
|
|
|
|
} else {
|
|
|
|
ignore_acpi_ev = 1;
|
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
} else {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"hotkey 0x%04x out of range for keyboard map\n",
|
|
|
|
hkey);
|
|
|
|
send_acpi_ev = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
/* 0x5000-0x5FFF: LID */
|
|
|
|
/* we don't handle it through this path, just
|
|
|
|
* eat up known LID events */
|
|
|
|
if (hkey != 0x5001 && hkey != 0x5002) {
|
|
|
|
printk(IBM_ERR
|
2007-09-23 22:39:04 +08:00
|
|
|
"unknown LID-related HKEY event: 0x%04x\n",
|
|
|
|
hkey);
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
send_acpi_ev = 1;
|
2007-09-23 22:39:05 +08:00
|
|
|
} else {
|
|
|
|
ignore_acpi_ev = 1;
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
/* 0x7000-0x7FFF: misc */
|
|
|
|
if (tp_features.hotkey_wlsw && hkey == 0x7000) {
|
|
|
|
tpacpi_input_send_radiosw();
|
2007-07-19 10:45:35 +08:00
|
|
|
break;
|
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* fallthrough to default */
|
|
|
|
default:
|
|
|
|
/* case 2: dock-related */
|
|
|
|
/* 0x2305 - T43 waking up due to bay lever eject while aslept */
|
|
|
|
/* case 3: ultra-bay related. maybe bay in dock? */
|
|
|
|
/* 0x3003 - T43 after wake up by bay lever eject (0x2305) */
|
|
|
|
printk(IBM_NOTICE "unhandled HKEY event 0x%04x\n", hkey);
|
|
|
|
send_acpi_ev = 1;
|
2007-07-19 10:45:35 +08:00
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2007-09-23 22:39:04 +08:00
|
|
|
/* Legacy events */
|
2007-09-23 22:39:05 +08:00
|
|
|
if (!ignore_acpi_ev && (send_acpi_ev || hotkey_report_mode < 2)) {
|
2007-09-23 22:39:04 +08:00
|
|
|
acpi_bus_generate_proc_event(ibm->acpi->device, event, hkey);
|
2007-09-23 22:39:05 +08:00
|
|
|
}
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
|
2007-09-23 22:39:04 +08:00
|
|
|
/* netlink events */
|
2007-09-23 22:39:05 +08:00
|
|
|
if (!ignore_acpi_ev && send_acpi_ev) {
|
2007-09-23 22:39:04 +08:00
|
|
|
acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
|
|
|
|
ibm->acpi->device->dev.bus_id,
|
|
|
|
event, hkey);
|
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:38 +08:00
|
|
|
static void hotkey_resume(void)
|
|
|
|
{
|
2008-01-08 23:02:39 +08:00
|
|
|
if (hotkey_mask_get())
|
|
|
|
printk(IBM_ERR "error while trying to read hot key mask from firmware\n");
|
2007-07-19 10:45:38 +08:00
|
|
|
tpacpi_input_send_radiosw();
|
2008-01-08 23:02:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
|
|
|
|
hotkey_poll_setup_safe(0);
|
|
|
|
#endif
|
2007-07-19 10:45:38 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:16 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2005-08-17 12:00:00 +08:00
|
|
|
static int hotkey_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
int res, status;
|
2005-04-17 06:20:36 +08:00
|
|
|
int len = 0;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.hotkey) {
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2008-01-08 23:02:39 +08:00
|
|
|
res = hotkey_status_get(&status);
|
|
|
|
if (!res)
|
|
|
|
res = hotkey_mask_get();
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_unlock(&hotkey_mutex);
|
2007-04-21 22:08:39 +08:00
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0));
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.hotkey_mask) {
|
2008-01-08 23:02:39 +08:00
|
|
|
len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_mask);
|
2005-04-17 06:20:36 +08:00
|
|
|
len += sprintf(p + len,
|
|
|
|
"commands:\tenable, disable, reset, <mask>\n");
|
|
|
|
} else {
|
|
|
|
len += sprintf(p + len, "mask:\t\tnot supported\n");
|
|
|
|
len += sprintf(p + len, "commands:\tenable, disable, reset\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int hotkey_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-19 10:45:29 +08:00
|
|
|
int res, status;
|
|
|
|
u32 mask;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *cmd;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.hotkey)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&hotkey_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
status = -1;
|
|
|
|
mask = hotkey_mask;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
res = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "enable") == 0) {
|
|
|
|
status = 1;
|
|
|
|
} else if (strlencmp(cmd, "disable") == 0) {
|
|
|
|
status = 0;
|
|
|
|
} else if (strlencmp(cmd, "reset") == 0) {
|
2005-08-17 12:00:00 +08:00
|
|
|
status = hotkey_orig_status;
|
|
|
|
mask = hotkey_orig_mask;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (sscanf(cmd, "0x%x", &mask) == 1) {
|
|
|
|
/* mask set */
|
|
|
|
} else if (sscanf(cmd, "%x", &mask) == 1) {
|
|
|
|
/* mask set */
|
2007-04-24 22:48:15 +08:00
|
|
|
} else {
|
|
|
|
res = -EINVAL;
|
|
|
|
goto errexit;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2008-01-08 23:02:39 +08:00
|
|
|
if (status != -1)
|
|
|
|
res = hotkey_status_set(status);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-08 23:02:39 +08:00
|
|
|
if (!res && mask != hotkey_mask)
|
|
|
|
res = hotkey_mask_set(mask);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
errexit:
|
|
|
|
mutex_unlock(&hotkey_mutex);
|
|
|
|
return res;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-23 20:44:41 +08:00
|
|
|
static const struct acpi_device_id ibm_htk_device_ids[] = {
|
|
|
|
{IBM_HKEY_HID, 0},
|
|
|
|
{"", 0},
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
|
2007-07-23 20:44:41 +08:00
|
|
|
.hid = ibm_htk_device_ids,
|
2007-04-21 22:08:37 +08:00
|
|
|
.notify = hotkey_notify,
|
|
|
|
.handle = &hkey_handle,
|
|
|
|
.type = ACPI_DEVICE_NOTIFY,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct hotkey_driver_data = {
|
|
|
|
.name = "hotkey",
|
|
|
|
.read = hotkey_read,
|
|
|
|
.write = hotkey_write,
|
|
|
|
.exit = hotkey_exit,
|
2007-07-19 10:45:38 +08:00
|
|
|
.resume = hotkey_resume,
|
2007-04-21 22:08:37 +08:00
|
|
|
.acpi = &ibm_hotkey_acpidriver,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Bluetooth subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* sysfs bluetooth enable ---------------------------------------------- */
|
|
|
|
static ssize_t bluetooth_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
status = bluetooth_get_radiosw();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t bluetooth_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
res = bluetooth_set_radiosw(t);
|
|
|
|
|
|
|
|
return (res) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_bluetooth_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(bluetooth_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:17 +08:00
|
|
|
bluetooth_enable_show, bluetooth_enable_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static struct attribute *bluetooth_attributes[] = {
|
|
|
|
&dev_attr_bluetooth_enable.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group bluetooth_attr_group = {
|
|
|
|
.attrs = bluetooth_attributes,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init bluetooth_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-04-28 09:00:17 +08:00
|
|
|
int res;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = 0;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing bluetooth subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(hkey);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
|
|
|
|
G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.bluetooth = hkey_handle &&
|
2007-04-21 22:08:40 +08:00
|
|
|
acpi_evalf(hkey_handle, &status, "GBDC", "qd");
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "bluetooth is %s, status 0x%02x\n",
|
|
|
|
str_supported(tp_features.bluetooth),
|
|
|
|
status);
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
if (tp_features.bluetooth) {
|
|
|
|
if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
|
|
|
|
/* no bluetooth hardware present in system */
|
|
|
|
tp_features.bluetooth = 0;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"bluetooth hardware not installed\n");
|
|
|
|
} else {
|
|
|
|
res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&bluetooth_attr_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
2007-04-21 22:08:40 +08:00
|
|
|
}
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
return (tp_features.bluetooth)? 0 : 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
static void bluetooth_exit(void)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&bluetooth_attr_group);
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
static int bluetooth_get_radiosw(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
if (!tp_features.bluetooth)
|
|
|
|
return -ENODEV;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bluetooth_set_radiosw(int radio_on)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!tp_features.bluetooth)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (radio_on)
|
|
|
|
status |= TP_ACPI_BLUETOOTH_RADIOSSW;
|
|
|
|
else
|
|
|
|
status &= ~TP_ACPI_BLUETOOTH_RADIOSSW;
|
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2005-08-17 12:00:00 +08:00
|
|
|
static int bluetooth_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = bluetooth_get_radiosw();
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.bluetooth)
|
2005-04-17 06:20:36 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
else {
|
2007-04-21 22:08:40 +08:00
|
|
|
len += sprintf(p + len, "status:\t\t%s\n",
|
|
|
|
(status)? "enabled" : "disabled");
|
2005-04-17 06:20:36 +08:00
|
|
|
len += sprintf(p + len, "commands:\tenable, disable\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int bluetooth_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.bluetooth)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "enable") == 0) {
|
2007-04-21 22:08:40 +08:00
|
|
|
bluetooth_set_radiosw(1);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "disable") == 0) {
|
2007-04-21 22:08:40 +08:00
|
|
|
bluetooth_set_radiosw(0);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct bluetooth_driver_data = {
|
|
|
|
.name = "bluetooth",
|
|
|
|
.read = bluetooth_read,
|
|
|
|
.write = bluetooth_write,
|
2007-04-28 09:00:17 +08:00
|
|
|
.exit = bluetooth_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Wan subdriver
|
|
|
|
*/
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* sysfs wan enable ---------------------------------------------------- */
|
|
|
|
static ssize_t wan_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
status = wan_get_radiosw();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t wan_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 1, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
res = wan_set_radiosw(t);
|
|
|
|
|
|
|
|
return (res) ? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_wan_enable =
|
2007-05-31 07:50:14 +08:00
|
|
|
__ATTR(wwan_enable, S_IWUSR | S_IRUGO,
|
2007-04-28 09:00:17 +08:00
|
|
|
wan_enable_show, wan_enable_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static struct attribute *wan_attributes[] = {
|
|
|
|
&dev_attr_wan_enable.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group wan_attr_group = {
|
|
|
|
.attrs = wan_attributes,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init wan_init(struct ibm_init_struct *iibm)
|
2006-06-02 05:41:00 +08:00
|
|
|
{
|
2007-04-28 09:00:17 +08:00
|
|
|
int res;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = 0;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing wan subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(hkey);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.wan = hkey_handle &&
|
2007-04-21 22:08:40 +08:00
|
|
|
acpi_evalf(hkey_handle, &status, "GWAN", "qd");
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "wan is %s, status 0x%02x\n",
|
|
|
|
str_supported(tp_features.wan),
|
|
|
|
status);
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
if (tp_features.wan) {
|
|
|
|
if (!(status & TP_ACPI_WANCARD_HWPRESENT)) {
|
|
|
|
/* no wan hardware present in system */
|
|
|
|
tp_features.wan = 0;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"wan hardware not installed\n");
|
|
|
|
} else {
|
|
|
|
res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&wan_attr_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
}
|
2007-04-21 22:08:40 +08:00
|
|
|
}
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
return (tp_features.wan)? 0 : 1;
|
2006-06-02 05:41:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
static void wan_exit(void)
|
|
|
|
{
|
|
|
|
sysfs_remove_group(&tpacpi_pdev->dev.kobj,
|
|
|
|
&wan_attr_group);
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
static int wan_get_radiosw(void)
|
2006-06-02 05:41:00 +08:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
if (!tp_features.wan)
|
|
|
|
return -ENODEV;
|
2006-06-02 05:41:00 +08:00
|
|
|
|
2007-04-21 22:08:40 +08:00
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wan_set_radiosw(int radio_on)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!tp_features.wan)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (radio_on)
|
|
|
|
status |= TP_ACPI_WANCARD_RADIOSSW;
|
|
|
|
else
|
|
|
|
status &= ~TP_ACPI_WANCARD_RADIOSSW;
|
|
|
|
if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return 0;
|
2006-06-02 05:41:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:17 +08:00
|
|
|
/* procfs -------------------------------------------------------------- */
|
2006-06-02 05:41:00 +08:00
|
|
|
static int wan_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
2007-04-21 22:08:40 +08:00
|
|
|
int status = wan_get_radiosw();
|
2006-06-02 05:41:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.wan)
|
2006-06-02 05:41:00 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
else {
|
2007-04-21 22:08:40 +08:00
|
|
|
len += sprintf(p + len, "status:\t\t%s\n",
|
|
|
|
(status)? "enabled" : "disabled");
|
2006-06-02 05:41:00 +08:00
|
|
|
len += sprintf(p + len, "commands:\tenable, disable\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wan_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.wan)
|
2006-06-02 05:41:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "enable") == 0) {
|
2007-04-21 22:08:40 +08:00
|
|
|
wan_set_radiosw(1);
|
2006-06-02 05:41:00 +08:00
|
|
|
} else if (strlencmp(cmd, "disable") == 0) {
|
2007-04-21 22:08:40 +08:00
|
|
|
wan_set_radiosw(0);
|
2006-06-02 05:41:00 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct wan_driver_data = {
|
|
|
|
.name = "wan",
|
|
|
|
.read = wan_read,
|
|
|
|
.write = wan_write,
|
2007-04-28 09:00:17 +08:00
|
|
|
.exit = wan_exit,
|
2007-04-21 22:08:35 +08:00
|
|
|
.flags.experimental = 1,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Video subdriver
|
|
|
|
*/
|
|
|
|
|
2006-11-26 02:36:00 +08:00
|
|
|
static enum video_access_mode video_supported;
|
|
|
|
static int video_orig_autosw;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(vid, root, "\\_SB.PCI.AGP.VGA", /* 570 */
|
|
|
|
"\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */
|
|
|
|
"\\_SB.PCI0.VID0", /* 770e */
|
|
|
|
"\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */
|
|
|
|
"\\_SB.PCI0.AGP.VID", /* all others */
|
|
|
|
); /* R30, R31 */
|
|
|
|
|
|
|
|
IBM_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init video_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-08-17 12:00:00 +08:00
|
|
|
int ivga;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(vid);
|
|
|
|
IBM_ACPIHANDLE_INIT(vid2);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
|
|
|
|
/* G41, assume IVGA doesn't change */
|
|
|
|
vid_handle = vid2_handle;
|
|
|
|
|
|
|
|
if (!vid_handle)
|
|
|
|
/* video switching not supported on R30, R31 */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_NONE;
|
2005-08-17 12:00:00 +08:00
|
|
|
else if (acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
|
|
|
|
/* 570 */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_570;
|
2005-08-17 12:00:00 +08:00
|
|
|
else if (acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
|
|
|
|
/* 600e/x, 770e, 770x */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_770;
|
2005-08-17 12:00:00 +08:00
|
|
|
else
|
|
|
|
/* all others */
|
2007-04-21 22:08:28 +08:00
|
|
|
video_supported = TPACPI_VIDEO_NEW;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
|
|
|
|
str_supported(video_supported != TPACPI_VIDEO_NONE),
|
|
|
|
video_supported);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void video_exit(void)
|
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"restoring original video autoswitch mode\n");
|
|
|
|
if (video_autosw_set(video_orig_autosw))
|
|
|
|
printk(IBM_ERR "error while trying to restore original "
|
|
|
|
"video autoswitch mode\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_get(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int status = 0;
|
|
|
|
int i;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
|
|
|
|
TP_ACPI_VIDEO_570_PHSCMD))
|
|
|
|
return -EIO;
|
|
|
|
status = i & TP_ACPI_VIDEO_570_PHSMASK;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDL", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_LCD;
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_CRT;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
|
|
|
|
!acpi_evalf(NULL, &i, "\\VCDC", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_CRT;
|
|
|
|
|
|
|
|
if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
|
|
|
|
!acpi_evalf(NULL, &i, "\\VCDL", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_LCD;
|
|
|
|
if (!acpi_evalf(NULL, &i, "\\VCDD", "d"))
|
|
|
|
return -EIO;
|
|
|
|
if (i)
|
|
|
|
status |= TP_ACPI_VIDEO_S_DVI;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_set(int status)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
res = acpi_evalf(NULL, NULL,
|
|
|
|
"\\_SB.PHS2", "vdd",
|
|
|
|
TP_ACPI_VIDEO_570_PHS2CMD,
|
|
|
|
status | TP_ACPI_VIDEO_570_PHS2SET);
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
autosw = video_autosw_get();
|
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(vid_handle, NULL,
|
|
|
|
"ASWT", "vdd", status * 0x100, 0);
|
|
|
|
if (!autosw && video_autosw_set(autosw)) {
|
|
|
|
printk(IBM_ERR "video auto-switch left enabled due to error\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
|
|
|
|
acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return (res)? 0 : -EIO;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_autosw_get(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw = 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d"))
|
|
|
|
return -EIO;
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d"))
|
|
|
|
return -EIO;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return autosw & 1;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_autosw_set(int enable)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
|
|
|
|
return -EIO;
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
static int video_outputsw_cycle(void)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int autosw = video_autosw_get();
|
|
|
|
int res;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(ec_handle, NULL, "_Q16", "v");
|
|
|
|
break;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = acpi_evalf(vid_handle, NULL, "VSWT", "v");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
if (!autosw && video_autosw_set(autosw)) {
|
|
|
|
printk(IBM_ERR "video auto-switch left enabled due to error\n");
|
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
return (res)? 0 : -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int video_expand_toggle(void)
|
|
|
|
{
|
|
|
|
switch (video_supported) {
|
|
|
|
case TPACPI_VIDEO_570:
|
|
|
|
return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
case TPACPI_VIDEO_770:
|
|
|
|
return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
case TPACPI_VIDEO_NEW:
|
|
|
|
return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
|
|
|
|
0 : -EIO;
|
|
|
|
default:
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
/* not reached */
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static int video_read(char *p)
|
|
|
|
{
|
2007-04-21 22:08:41 +08:00
|
|
|
int status, autosw;
|
2007-03-24 04:33:57 +08:00
|
|
|
int len = 0;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NONE) {
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
status = video_outputsw_get();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
autosw = video_autosw_get();
|
|
|
|
if (autosw < 0)
|
|
|
|
return autosw;
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tsupported\n");
|
|
|
|
len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0));
|
|
|
|
len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1));
|
2007-04-21 22:08:28 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NEW)
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3));
|
|
|
|
len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0));
|
|
|
|
len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n");
|
|
|
|
len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NEW)
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n");
|
|
|
|
len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n");
|
|
|
|
len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n");
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int video_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int enable, disable, status;
|
2007-04-21 22:08:41 +08:00
|
|
|
int res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
if (video_supported == TPACPI_VIDEO_NONE)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2007-04-21 22:08:41 +08:00
|
|
|
enable = 0;
|
|
|
|
disable = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "lcd_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_LCD;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "lcd_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_LCD;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "crt_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_CRT;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "crt_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_CRT;
|
2007-04-21 22:08:28 +08:00
|
|
|
} else if (video_supported == TPACPI_VIDEO_NEW &&
|
2005-08-17 12:00:00 +08:00
|
|
|
strlencmp(cmd, "dvi_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
enable |= TP_ACPI_VIDEO_S_DVI;
|
2007-04-21 22:08:28 +08:00
|
|
|
} else if (video_supported == TPACPI_VIDEO_NEW &&
|
2005-08-17 12:00:00 +08:00
|
|
|
strlencmp(cmd, "dvi_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
disable |= TP_ACPI_VIDEO_S_DVI;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "auto_enable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(1);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "auto_disable") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_autosw_set(0);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "video_switch") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_outputsw_cycle();
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strlencmp(cmd, "expand_toggle") == 0) {
|
2007-04-21 22:08:41 +08:00
|
|
|
res = video_expand_toggle();
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable || disable) {
|
2007-04-21 22:08:41 +08:00
|
|
|
status = video_outputsw_get();
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
res = video_outputsw_set((status & ~disable) | enable);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct video_driver_data = {
|
|
|
|
.name = "video",
|
|
|
|
.read = video_read,
|
|
|
|
.write = video_write,
|
|
|
|
.exit = video_exit,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Light (thinklight) subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */
|
|
|
|
IBM_HANDLE(ledb, ec, "LEDB"); /* G4x */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init light_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(ledb);
|
|
|
|
IBM_ACPIHANDLE_INIT(lght);
|
|
|
|
IBM_ACPIHANDLE_INIT(cmos);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.light = (cmos_handle || lght_handle) && !ledb_handle;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.light)
|
2005-08-17 12:00:00 +08:00
|
|
|
/* light status not supported on
|
|
|
|
570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.light_status =
|
|
|
|
acpi_evalf(ec_handle, NULL, "KBLT", "qv");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "light is %s\n",
|
2007-04-21 22:08:36 +08:00
|
|
|
str_supported(tp_features.light));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
return (tp_features.light)? 0 : 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int light_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int status = 0;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.light) {
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
2007-04-21 22:08:36 +08:00
|
|
|
} else if (!tp_features.light_status) {
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "status:\t\tunknown\n");
|
|
|
|
len += sprintf(p + len, "commands:\ton, off\n");
|
|
|
|
} else {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
|
|
|
|
return -EIO;
|
|
|
|
len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0));
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "commands:\ton, off\n");
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int light_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int cmos_cmd, lght_cmd;
|
|
|
|
char *cmd;
|
|
|
|
int success;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.light)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "on") == 0) {
|
|
|
|
cmos_cmd = 0x0c;
|
|
|
|
lght_cmd = 1;
|
|
|
|
} else if (strlencmp(cmd, "off") == 0) {
|
|
|
|
cmos_cmd = 0x0d;
|
|
|
|
lght_cmd = 0;
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
success = cmos_handle ?
|
2005-08-17 12:00:00 +08:00
|
|
|
acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) :
|
|
|
|
acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!success)
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct light_driver_data = {
|
|
|
|
.name = "light",
|
|
|
|
.read = light_read,
|
|
|
|
.write = light_write,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Dock subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-29 12:58:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DOCK
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
IBM_HANDLE(dock, root, "\\_SB.GDCK", /* X30, X31, X40 */
|
|
|
|
"\\_SB.PCI0.DOCK", /* 600e/x,770e,770x,A2xm/p,T20-22,X20-21 */
|
|
|
|
"\\_SB.PCI0.PCI1.DOCK", /* all others */
|
|
|
|
"\\_SB.PCI.ISA.SLCE", /* 570 */
|
|
|
|
); /* A21e,G4x,R30,R31,R32,R40,R40e,R50e */
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
/* don't list other alternatives as we install a notify handler on the 570 */
|
|
|
|
IBM_HANDLE(pci, root, "\\_SB.PCI"); /* 570 */
|
|
|
|
|
2007-07-23 20:44:41 +08:00
|
|
|
static const struct acpi_device_id ibm_pci_device_ids[] = {
|
|
|
|
{PCI_ROOT_HID_STRING, 0},
|
|
|
|
{"", 0},
|
|
|
|
};
|
|
|
|
|
2007-04-28 09:00:15 +08:00
|
|
|
static struct tp_acpi_drv_struct ibm_dock_acpidriver[2] = {
|
|
|
|
{
|
|
|
|
.notify = dock_notify,
|
|
|
|
.handle = &dock_handle,
|
|
|
|
.type = ACPI_SYSTEM_NOTIFY,
|
|
|
|
},
|
|
|
|
{
|
2007-07-19 10:45:40 +08:00
|
|
|
/* THIS ONE MUST NEVER BE USED FOR DRIVER AUTOLOADING.
|
|
|
|
* We just use it to get notifications of dock hotplug
|
|
|
|
* in very old thinkpads */
|
2007-07-23 20:44:41 +08:00
|
|
|
.hid = ibm_pci_device_ids,
|
2007-04-28 09:00:15 +08:00
|
|
|
.notify = dock_notify,
|
|
|
|
.handle = &pci_handle,
|
|
|
|
.type = ACPI_SYSTEM_NOTIFY,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ibm_struct dock_driver_data[2] = {
|
|
|
|
{
|
|
|
|
.name = "dock",
|
|
|
|
.read = dock_read,
|
|
|
|
.write = dock_write,
|
|
|
|
.acpi = &ibm_dock_acpidriver[0],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "dock",
|
|
|
|
.acpi = &ibm_dock_acpidriver[1],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#define dock_docked() (_sta(dock_handle) & 1)
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init dock_init(struct ibm_init_struct *iibm)
|
2007-04-21 22:08:31 +08:00
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(dock);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "dock is %s\n",
|
|
|
|
str_supported(dock_handle != NULL));
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (dock_handle)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2007-04-28 09:00:15 +08:00
|
|
|
static int __init dock_init2(struct ibm_init_struct *iibm)
|
|
|
|
{
|
|
|
|
int dock2_needed;
|
|
|
|
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing dock subdriver part 2\n");
|
|
|
|
|
|
|
|
if (dock_driver_data[0].flags.acpi_driver_registered &&
|
|
|
|
dock_driver_data[0].flags.acpi_notify_installed) {
|
|
|
|
IBM_ACPIHANDLE_INIT(pci);
|
|
|
|
dock2_needed = (pci_handle != NULL);
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"dock PCI handler for the TP 570 is %s\n",
|
|
|
|
str_supported(dock2_needed));
|
|
|
|
} else {
|
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"dock subdriver part 2 not required\n");
|
|
|
|
dock2_needed = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (dock2_needed)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void dock_notify(struct ibm_struct *ibm, u32 event)
|
|
|
|
{
|
|
|
|
int docked = dock_docked();
|
2007-07-23 20:44:41 +08:00
|
|
|
int pci = ibm->acpi->hid && ibm->acpi->device &&
|
|
|
|
acpi_match_device_ids(ibm->acpi->device, ibm_pci_device_ids);
|
2007-08-23 01:24:31 +08:00
|
|
|
int data;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
if (event == 1 && !pci) /* 570 */
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 1; /* button */
|
2007-03-24 04:33:57 +08:00
|
|
|
else if (event == 1 && pci) /* 570 */
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 3; /* dock */
|
2007-03-24 04:33:57 +08:00
|
|
|
else if (event == 3 && docked)
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 1; /* button */
|
2007-03-24 04:33:57 +08:00
|
|
|
else if (event == 3 && !docked)
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 2; /* undock */
|
2007-03-24 04:33:57 +08:00
|
|
|
else if (event == 0 && docked)
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 3; /* dock */
|
2007-03-24 04:33:57 +08:00
|
|
|
else {
|
|
|
|
printk(IBM_ERR "unknown dock event %d, status %d\n",
|
|
|
|
event, _sta(dock_handle));
|
2007-08-23 01:24:31 +08:00
|
|
|
data = 0; /* unknown */
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
2007-08-24 03:20:26 +08:00
|
|
|
acpi_bus_generate_proc_event(ibm->acpi->device, event, data);
|
2007-08-23 01:24:31 +08:00
|
|
|
acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
|
|
|
|
ibm->acpi->device->dev.bus_id,
|
|
|
|
event, data);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int dock_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int docked = dock_docked();
|
|
|
|
|
|
|
|
if (!dock_handle)
|
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
else if (!docked)
|
|
|
|
len += sprintf(p + len, "status:\t\tundocked\n");
|
|
|
|
else {
|
|
|
|
len += sprintf(p + len, "status:\t\tdocked\n");
|
|
|
|
len += sprintf(p + len, "commands:\tdock, undock\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int dock_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
|
|
|
|
if (!dock_docked())
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (strlencmp(cmd, "undock") == 0) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 0) ||
|
|
|
|
!acpi_evalf(dock_handle, NULL, "_EJ0", "vd", 1))
|
2007-03-24 04:33:57 +08:00
|
|
|
return -EIO;
|
|
|
|
} else if (strlencmp(cmd, "dock") == 0) {
|
|
|
|
if (!acpi_evalf(dock_handle, NULL, "_DCK", "vd", 1))
|
|
|
|
return -EIO;
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-03-29 12:58:41 +08:00
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_DOCK */
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* Bay subdriver
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-29 12:58:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_BAY
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(bay, root, "\\_SB.PCI.IDE.SECN.MAST", /* 570 */
|
|
|
|
"\\_SB.PCI0.IDE0.IDES.IDSM", /* 600e/x, 770e, 770x */
|
|
|
|
"\\_SB.PCI0.SATA.SCND.MSTR", /* T60, X60, Z60 */
|
|
|
|
"\\_SB.PCI0.IDE0.SCND.MSTR", /* all others */
|
|
|
|
); /* A21e, R30, R31 */
|
|
|
|
IBM_HANDLE(bay_ej, bay, "_EJ3", /* 600e/x, A2xm/p, A3x */
|
|
|
|
"_EJ0", /* all others */
|
|
|
|
); /* 570,A21e,G4x,R30,R31,R32,R40e,R50e */
|
|
|
|
IBM_HANDLE(bay2, root, "\\_SB.PCI0.IDE0.PRIM.SLAV", /* A3x, R32 */
|
|
|
|
"\\_SB.PCI0.IDE0.IDEP.IDPS", /* 600e/x, 770e, 770x */
|
|
|
|
); /* all others */
|
|
|
|
IBM_HANDLE(bay2_ej, bay2, "_EJ3", /* 600e/x, 770e, A3x */
|
|
|
|
"_EJ0", /* 770x */
|
|
|
|
); /* all others */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init bay_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing bay subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(bay);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (bay_handle)
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(bay_ej);
|
|
|
|
IBM_ACPIHANDLE_INIT(bay2);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (bay2_handle)
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(bay2_ej);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.bay_status = bay_handle &&
|
|
|
|
acpi_evalf(bay_handle, NULL, "_STA", "qv");
|
|
|
|
tp_features.bay_status2 = bay2_handle &&
|
|
|
|
acpi_evalf(bay2_handle, NULL, "_STA", "qv");
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.bay_eject = bay_handle && bay_ej_handle &&
|
|
|
|
(strlencmp(bay_ej_path, "_EJ0") == 0 || experimental);
|
|
|
|
tp_features.bay_eject2 = bay2_handle && bay2_ej_handle &&
|
|
|
|
(strlencmp(bay2_ej_path, "_EJ0") == 0 || experimental);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"bay 1: status %s, eject %s; bay 2: status %s, eject %s\n",
|
2007-04-21 22:08:36 +08:00
|
|
|
str_supported(tp_features.bay_status),
|
|
|
|
str_supported(tp_features.bay_eject),
|
|
|
|
str_supported(tp_features.bay_status2),
|
|
|
|
str_supported(tp_features.bay_eject2));
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
return (tp_features.bay_status || tp_features.bay_eject ||
|
|
|
|
tp_features.bay_status2 || tp_features.bay_eject2)? 0 : 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void bay_notify(struct ibm_struct *ibm, u32 event)
|
|
|
|
{
|
2007-08-24 03:20:26 +08:00
|
|
|
acpi_bus_generate_proc_event(ibm->acpi->device, event, 0);
|
2007-08-23 01:24:31 +08:00
|
|
|
acpi_bus_generate_netlink_event(ibm->acpi->device->pnp.device_class,
|
|
|
|
ibm->acpi->device->dev.bus_id,
|
|
|
|
event, 0);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
#define bay_occupied(b) (_sta(b##_handle) & 1)
|
|
|
|
|
|
|
|
static int bay_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
int occupied = bay_occupied(bay);
|
|
|
|
int occupied2 = bay_occupied(bay2);
|
|
|
|
int eject, eject2;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
len += sprintf(p + len, "status:\t\t%s\n",
|
|
|
|
tp_features.bay_status ?
|
|
|
|
(occupied ? "occupied" : "unoccupied") :
|
|
|
|
"not supported");
|
|
|
|
if (tp_features.bay_status2)
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "status2:\t%s\n", occupied2 ?
|
|
|
|
"occupied" : "unoccupied");
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
eject = tp_features.bay_eject && occupied;
|
|
|
|
eject2 = tp_features.bay_eject2 && occupied2;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
if (eject && eject2)
|
|
|
|
len += sprintf(p + len, "commands:\teject, eject2\n");
|
|
|
|
else if (eject)
|
2005-04-17 06:20:36 +08:00
|
|
|
len += sprintf(p + len, "commands:\teject\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
else if (eject2)
|
|
|
|
len += sprintf(p + len, "commands:\teject2\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int bay_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (!tp_features.bay_eject && !tp_features.bay_eject2)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2007-04-21 22:08:36 +08:00
|
|
|
if (tp_features.bay_eject && strlencmp(cmd, "eject") == 0) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!acpi_evalf(bay_ej_handle, NULL, NULL, "vd", 1))
|
|
|
|
return -EIO;
|
2007-04-21 22:08:36 +08:00
|
|
|
} else if (tp_features.bay_eject2 &&
|
2005-08-17 12:00:00 +08:00
|
|
|
strlencmp(cmd, "eject2") == 0) {
|
|
|
|
if (!acpi_evalf(bay2_ej_handle, NULL, NULL, "vd", 1))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EIO;
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2007-04-21 22:08:33 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
static struct tp_acpi_drv_struct ibm_bay_acpidriver = {
|
|
|
|
.notify = bay_notify,
|
|
|
|
.handle = &bay_handle,
|
|
|
|
.type = ACPI_SYSTEM_NOTIFY,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct bay_driver_data = {
|
|
|
|
.name = "bay",
|
|
|
|
.read = bay_read,
|
|
|
|
.write = bay_write,
|
2007-04-21 22:08:37 +08:00
|
|
|
.acpi = &ibm_bay_acpidriver,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-29 12:58:41 +08:00
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_BAY */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* CMOS subdriver
|
|
|
|
*/
|
|
|
|
|
2007-04-24 22:48:19 +08:00
|
|
|
/* sysfs cmos_command -------------------------------------------------- */
|
|
|
|
static ssize_t cmos_command_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long cmos_cmd;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 21, &cmos_cmd))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
res = issue_thinkpad_cmos_command(cmos_cmd);
|
|
|
|
return (res)? res : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_cmos_command =
|
|
|
|
__ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init cmos_init(struct ibm_init_struct *iibm)
|
2007-04-21 22:08:31 +08:00
|
|
|
{
|
2007-04-24 22:48:19 +08:00
|
|
|
int res;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"initializing cmos commands subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(cmos);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
|
|
|
|
str_supported(cmos_handle != NULL));
|
2007-04-24 22:48:19 +08:00
|
|
|
|
|
|
|
res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (cmos_handle)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:19 +08:00
|
|
|
static void cmos_exit(void)
|
|
|
|
{
|
|
|
|
device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int cmos_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
|
|
|
|
R30, R31, T20-22, X20-21 */
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!cmos_handle)
|
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
else {
|
|
|
|
len += sprintf(p + len, "status:\t\tsupported\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int cmos_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
2007-04-21 22:08:42 +08:00
|
|
|
int cmos_cmd, res;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
|
|
|
|
cmos_cmd >= 0 && cmos_cmd <= 21) {
|
2005-04-17 06:20:36 +08:00
|
|
|
/* cmos_cmd set */
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
res = issue_thinkpad_cmos_command(cmos_cmd);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct cmos_driver_data = {
|
|
|
|
.name = "cmos",
|
|
|
|
.read = cmos_read,
|
|
|
|
.write = cmos_write,
|
2007-04-24 22:48:19 +08:00
|
|
|
.exit = cmos_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* LED subdriver
|
|
|
|
*/
|
|
|
|
|
2006-11-26 02:36:00 +08:00
|
|
|
static enum led_access_mode led_supported;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(led, ec, "SLED", /* 570 */
|
|
|
|
"SYSL", /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
|
|
|
"LED", /* all others */
|
|
|
|
); /* R30, R31 */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init led_init(struct ibm_init_struct *iibm)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(led);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!led_handle)
|
|
|
|
/* led not supported on R30, R31 */
|
2007-04-21 22:08:28 +08:00
|
|
|
led_supported = TPACPI_LED_NONE;
|
2005-08-17 12:00:00 +08:00
|
|
|
else if (strlencmp(led_path, "SLED") == 0)
|
|
|
|
/* 570 */
|
2007-04-21 22:08:28 +08:00
|
|
|
led_supported = TPACPI_LED_570;
|
2005-08-17 12:00:00 +08:00
|
|
|
else if (strlencmp(led_path, "SYSL") == 0)
|
|
|
|
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
|
2007-04-21 22:08:28 +08:00
|
|
|
led_supported = TPACPI_LED_OLD;
|
2005-08-17 12:00:00 +08:00
|
|
|
else
|
|
|
|
/* all others */
|
2007-04-21 22:08:28 +08:00
|
|
|
led_supported = TPACPI_LED_NEW;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
|
|
|
|
str_supported(led_supported), led_supported);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (led_supported != TPACPI_LED_NONE)? 0 : 1;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking"))
|
|
|
|
|
|
|
|
static int led_read(char *p)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!led_supported) {
|
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
len += sprintf(p + len, "status:\t\tsupported\n");
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (led_supported == TPACPI_LED_570) {
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 570 */
|
|
|
|
int i, status;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
if (!acpi_evalf(ec_handle,
|
|
|
|
&status, "GLED", "dd", 1 << i))
|
|
|
|
return -EIO;
|
|
|
|
len += sprintf(p + len, "%d:\t\t%s\n",
|
|
|
|
i, led_status(status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
len += sprintf(p + len, "commands:\t"
|
2005-08-17 12:00:00 +08:00
|
|
|
"<led> on, <led> off, <led> blink (<led> is 0-7)\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
/* off, on, blink */
|
|
|
|
static const int led_sled_arg1[] = { 0, 1, 3 };
|
|
|
|
static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */
|
|
|
|
static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */
|
|
|
|
static const int led_led_arg1[] = { 0, 0x80, 0xc0 };
|
|
|
|
|
|
|
|
static int led_write(char *buf)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char *cmd;
|
2005-08-17 12:00:00 +08:00
|
|
|
int led, ind, ret;
|
|
|
|
|
|
|
|
if (!led_supported)
|
|
|
|
return -ENODEV;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (strstr(cmd, "off")) {
|
|
|
|
ind = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (strstr(cmd, "on")) {
|
2005-08-17 12:00:00 +08:00
|
|
|
ind = 1;
|
|
|
|
} else if (strstr(cmd, "blink")) {
|
|
|
|
ind = 2;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (led_supported == TPACPI_LED_570) {
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 570 */
|
|
|
|
led = 1 << led;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
|
2005-08-17 12:00:00 +08:00
|
|
|
led, led_sled_arg1[ind]))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EIO;
|
2007-04-21 22:08:28 +08:00
|
|
|
} else if (led_supported == TPACPI_LED_OLD) {
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
|
|
|
|
led = 1 << led;
|
2007-04-21 22:08:28 +08:00
|
|
|
ret = ec_write(TPACPI_LED_EC_HLMS, led);
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ret >= 0)
|
|
|
|
ret =
|
2007-04-21 22:08:28 +08:00
|
|
|
ec_write(TPACPI_LED_EC_HLBL,
|
2007-03-24 04:33:55 +08:00
|
|
|
led * led_exp_hlbl[ind]);
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ret >= 0)
|
|
|
|
ret =
|
2007-04-21 22:08:28 +08:00
|
|
|
ec_write(TPACPI_LED_EC_HLCL,
|
2007-03-24 04:33:55 +08:00
|
|
|
led * led_exp_hlcl[ind]);
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
/* all others */
|
|
|
|
if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
|
|
|
|
led, led_led_arg1[ind]))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct led_driver_data = {
|
|
|
|
.name = "led",
|
|
|
|
.read = led_read,
|
|
|
|
.write = led_write,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Beep subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
IBM_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init beep_init(struct ibm_init_struct *iibm)
|
2007-04-21 22:08:31 +08:00
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(beep);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
|
|
|
|
str_supported(beep_handle != NULL));
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return (beep_handle)? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int beep_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
|
|
|
|
if (!beep_handle)
|
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
else {
|
|
|
|
len += sprintf(p + len, "status:\t\tsupported\n");
|
|
|
|
len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int beep_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int beep_cmd;
|
|
|
|
|
|
|
|
if (!beep_handle)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
|
|
|
|
beep_cmd >= 0 && beep_cmd <= 17) {
|
|
|
|
/* beep_cmd set */
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct beep_driver_data = {
|
|
|
|
.name = "beep",
|
|
|
|
.read = beep_read,
|
|
|
|
.write = beep_write,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Thermal subdriver
|
|
|
|
*/
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:08 +08:00
|
|
|
static enum thermal_access_mode thermal_read_mode;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-24 22:48:16 +08:00
|
|
|
/* sysfs temp##_input -------------------------------------------------- */
|
|
|
|
|
|
|
|
static ssize_t thermal_temp_input_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct sensor_device_attribute *sensor_attr =
|
|
|
|
to_sensor_dev_attr(attr);
|
|
|
|
int idx = sensor_attr->index;
|
|
|
|
s32 value;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
res = thermal_get_sensor(idx, &value);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
if (value == TP_EC_THERMAL_TMP_NA * 1000)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
|
|
|
|
SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, thermal_temp_input_show, NULL, _idxB)
|
|
|
|
|
|
|
|
static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(1, 0),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(2, 1),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(3, 2),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(4, 3),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(5, 4),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(6, 5),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(7, 6),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(8, 7),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(9, 8),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(10, 9),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(11, 10),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(12, 11),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(13, 12),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(14, 13),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(15, 14),
|
|
|
|
THERMAL_SENSOR_ATTR_TEMP(16, 15),
|
|
|
|
};
|
|
|
|
|
|
|
|
#define THERMAL_ATTRS(X) \
|
|
|
|
&sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
|
|
|
|
|
|
|
|
static struct attribute *thermal_temp_input_attr[] = {
|
|
|
|
THERMAL_ATTRS(8),
|
|
|
|
THERMAL_ATTRS(9),
|
|
|
|
THERMAL_ATTRS(10),
|
|
|
|
THERMAL_ATTRS(11),
|
|
|
|
THERMAL_ATTRS(12),
|
|
|
|
THERMAL_ATTRS(13),
|
|
|
|
THERMAL_ATTRS(14),
|
|
|
|
THERMAL_ATTRS(15),
|
|
|
|
THERMAL_ATTRS(0),
|
|
|
|
THERMAL_ATTRS(1),
|
|
|
|
THERMAL_ATTRS(2),
|
|
|
|
THERMAL_ATTRS(3),
|
|
|
|
THERMAL_ATTRS(4),
|
|
|
|
THERMAL_ATTRS(5),
|
|
|
|
THERMAL_ATTRS(6),
|
|
|
|
THERMAL_ATTRS(7),
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group thermal_temp_input16_group = {
|
|
|
|
.attrs = thermal_temp_input_attr
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group thermal_temp_input8_group = {
|
|
|
|
.attrs = &thermal_temp_input_attr[8]
|
|
|
|
};
|
|
|
|
|
|
|
|
#undef THERMAL_SENSOR_ATTR_TEMP
|
|
|
|
#undef THERMAL_ATTRS
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init thermal_init(struct ibm_init_struct *iibm)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2006-11-24 21:47:08 +08:00
|
|
|
u8 t, ta1, ta2;
|
|
|
|
int i;
|
2007-04-21 22:08:31 +08:00
|
|
|
int acpi_tmp7;
|
2007-04-24 22:48:16 +08:00
|
|
|
int res;
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
|
2006-11-24 21:47:08 +08:00
|
|
|
|
2007-07-19 10:45:46 +08:00
|
|
|
if (thinkpad_id.ec_model) {
|
2006-11-24 21:47:08 +08:00
|
|
|
/*
|
|
|
|
* Direct EC access mode: sensors at registers
|
|
|
|
* 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
|
|
|
|
* non-implemented, thermal sensors return 0x80 when
|
|
|
|
* not available
|
|
|
|
*/
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:08 +08:00
|
|
|
ta1 = ta2 = 0;
|
|
|
|
for (i = 0; i < 8; i++) {
|
2007-04-21 22:08:43 +08:00
|
|
|
if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
|
2006-11-24 21:47:08 +08:00
|
|
|
ta1 |= t;
|
|
|
|
} else {
|
|
|
|
ta1 = 0;
|
|
|
|
break;
|
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
|
2006-11-24 21:47:08 +08:00
|
|
|
ta2 |= t;
|
|
|
|
} else {
|
|
|
|
ta1 = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ta1 == 0) {
|
|
|
|
/* This is sheer paranoia, but we handle it anyway */
|
|
|
|
if (acpi_tmp7) {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"ThinkPad ACPI EC access misbehaving, "
|
|
|
|
"falling back to ACPI TMPx access mode\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
|
2006-11-24 21:47:08 +08:00
|
|
|
} else {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"ThinkPad ACPI EC access misbehaving, "
|
|
|
|
"disabling thermal sensors access\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_NONE;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
thermal_read_mode =
|
|
|
|
(ta2 != 0) ?
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else if (acpi_tmp7) {
|
2006-11-24 21:47:08 +08:00
|
|
|
if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
|
|
|
|
/* 600e/x, 770e, 770x */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
|
2006-11-24 21:47:08 +08:00
|
|
|
} else {
|
|
|
|
/* Standard ACPI TMPx access, max 8 sensors */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* temperatures not supported on 570, G4x, R30, R31, R32 */
|
2007-04-21 22:08:28 +08:00
|
|
|
thermal_read_mode = TPACPI_THERMAL_NONE;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
|
|
|
|
str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
|
|
|
|
thermal_read_mode);
|
|
|
|
|
2007-04-24 22:48:16 +08:00
|
|
|
switch(thermal_read_mode) {
|
|
|
|
case TPACPI_THERMAL_TPEC_16:
|
2007-09-25 17:38:03 +08:00
|
|
|
res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input16_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_TPEC_8:
|
|
|
|
case TPACPI_THERMAL_ACPI_TMP07:
|
|
|
|
case TPACPI_THERMAL_ACPI_UPDT:
|
2007-09-25 17:38:03 +08:00
|
|
|
res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input8_group);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_NONE:
|
|
|
|
default:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void thermal_exit(void)
|
|
|
|
{
|
|
|
|
switch(thermal_read_mode) {
|
|
|
|
case TPACPI_THERMAL_TPEC_16:
|
2007-09-25 17:38:03 +08:00
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input16_group);
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_TPEC_8:
|
|
|
|
case TPACPI_THERMAL_ACPI_TMP07:
|
|
|
|
case TPACPI_THERMAL_ACPI_UPDT:
|
2007-09-25 17:38:03 +08:00
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:16 +08:00
|
|
|
&thermal_temp_input16_group);
|
|
|
|
break;
|
|
|
|
case TPACPI_THERMAL_NONE:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:43 +08:00
|
|
|
/* idx is zero-based */
|
|
|
|
static int thermal_get_sensor(int idx, s32 *value)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-21 22:08:43 +08:00
|
|
|
int t;
|
2006-11-24 21:47:08 +08:00
|
|
|
s8 tmp;
|
2007-04-21 22:08:43 +08:00
|
|
|
char tmpi[5];
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:43 +08:00
|
|
|
t = TP_EC_THERMAL_TMP0;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:08 +08:00
|
|
|
switch (thermal_read_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
#if TPACPI_MAX_THERMAL_SENSORS >= 16
|
|
|
|
case TPACPI_THERMAL_TPEC_16:
|
2007-04-21 22:08:43 +08:00
|
|
|
if (idx >= 8 && idx <= 15) {
|
|
|
|
t = TP_EC_THERMAL_TMP8;
|
|
|
|
idx -= 8;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
/* fallthrough */
|
|
|
|
#endif
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_THERMAL_TPEC_8:
|
2007-04-21 22:08:43 +08:00
|
|
|
if (idx <= 7) {
|
|
|
|
if (!acpi_ec_read(t + idx, &tmp))
|
2005-08-17 12:00:00 +08:00
|
|
|
return -EIO;
|
2007-04-21 22:08:43 +08:00
|
|
|
*value = tmp * 1000;
|
|
|
|
return 0;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_THERMAL_ACPI_UPDT:
|
2007-04-21 22:08:43 +08:00
|
|
|
if (idx <= 7) {
|
|
|
|
snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
|
|
|
|
if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
|
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
|
|
|
|
return -EIO;
|
2007-04-21 22:08:43 +08:00
|
|
|
*value = (t - 2732) * 100;
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_THERMAL_ACPI_TMP07:
|
2007-04-21 22:08:43 +08:00
|
|
|
if (idx <= 7) {
|
|
|
|
snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
|
2005-08-17 12:00:00 +08:00
|
|
|
if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
|
|
|
|
return -EIO;
|
2007-07-19 10:45:45 +08:00
|
|
|
if (t > 127 || t < -127)
|
|
|
|
t = TP_EC_THERMAL_TMP_NA;
|
2007-04-21 22:08:43 +08:00
|
|
|
*value = t * 1000;
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_THERMAL_NONE:
|
2006-11-24 21:47:08 +08:00
|
|
|
default:
|
2007-04-21 22:08:43 +08:00
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
|
|
|
|
{
|
|
|
|
int res, i;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
n = 8;
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
if (!s)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
|
|
|
|
n = 16;
|
|
|
|
|
|
|
|
for(i = 0 ; i < n; i++) {
|
|
|
|
res = thermal_get_sensor(i, &s->temp[i]);
|
|
|
|
if (res)
|
|
|
|
return res;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2007-04-21 22:08:43 +08:00
|
|
|
|
|
|
|
return n;
|
2006-11-24 21:47:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int thermal_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int n, i;
|
|
|
|
struct ibm_thermal_sensors_struct t;
|
|
|
|
|
|
|
|
n = thermal_get_sensors(&t);
|
|
|
|
if (unlikely(n < 0))
|
|
|
|
return n;
|
|
|
|
|
|
|
|
len += sprintf(p + len, "temperatures:\t");
|
|
|
|
|
|
|
|
if (n > 0) {
|
|
|
|
for (i = 0; i < (n - 1); i++)
|
|
|
|
len += sprintf(p + len, "%d ", t.temp[i] / 1000);
|
|
|
|
len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
|
|
|
|
} else
|
|
|
|
len += sprintf(p + len, "not supported\n");
|
2005-08-17 12:00:00 +08:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct thermal_driver_data = {
|
|
|
|
.name = "thermal",
|
|
|
|
.read = thermal_read,
|
2007-04-24 22:48:16 +08:00
|
|
|
.exit = thermal_exit,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* EC Dump subdriver
|
|
|
|
*/
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static u8 ecdump_regs[256];
|
|
|
|
|
|
|
|
static int ecdump_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int i, j;
|
|
|
|
u8 v;
|
|
|
|
|
|
|
|
len += sprintf(p + len, "EC "
|
|
|
|
" +00 +01 +02 +03 +04 +05 +06 +07"
|
|
|
|
" +08 +09 +0a +0b +0c +0d +0e +0f\n");
|
|
|
|
for (i = 0; i < 256; i += 16) {
|
|
|
|
len += sprintf(p + len, "EC 0x%02x:", i);
|
|
|
|
for (j = 0; j < 16; j++) {
|
|
|
|
if (!acpi_ec_read(i + j, &v))
|
|
|
|
break;
|
|
|
|
if (v != ecdump_regs[i + j])
|
|
|
|
len += sprintf(p + len, " *%02x", v);
|
|
|
|
else
|
|
|
|
len += sprintf(p + len, " %02x", v);
|
|
|
|
ecdump_regs[i + j] = v;
|
|
|
|
}
|
|
|
|
len += sprintf(p + len, "\n");
|
|
|
|
if (j != 16)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* These are way too dangerous to advertise openly... */
|
|
|
|
#if 0
|
|
|
|
len += sprintf(p + len, "commands:\t0x<offset> 0x<value>"
|
|
|
|
" (<offset> is 00-ff, <value> is 00-ff)\n");
|
|
|
|
len += sprintf(p + len, "commands:\t0x<offset> <value> "
|
|
|
|
" (<offset> is 00-ff, <value> is 0-255)\n");
|
|
|
|
#endif
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ecdump_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int i, v;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) {
|
|
|
|
/* i and v set */
|
|
|
|
} else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) {
|
|
|
|
/* i and v set */
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
if (i >= 0 && i < 256 && v >= 0 && v < 256) {
|
|
|
|
if (!acpi_ec_write(i, v))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EIO;
|
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct ecdump_driver_data = {
|
|
|
|
.name = "ecdump",
|
|
|
|
.read = ecdump_read,
|
|
|
|
.write = ecdump_write,
|
2007-04-21 22:08:35 +08:00
|
|
|
.flags.experimental = 1,
|
2007-04-21 22:08:33 +08:00
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Backlight/brightness subdriver
|
|
|
|
*/
|
|
|
|
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct backlight_device *ibm_backlight_device;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
static struct backlight_ops ibm_backlight_data = {
|
|
|
|
.get_brightness = brightness_get,
|
|
|
|
.update_status = brightness_update_status,
|
|
|
|
};
|
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
static struct mutex brightness_mutex;
|
|
|
|
|
2007-10-31 03:46:20 +08:00
|
|
|
static int __init tpacpi_query_bcll_levels(acpi_handle handle)
|
|
|
|
{
|
|
|
|
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
|
|
union acpi_object *obj;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (ACPI_SUCCESS(acpi_evaluate_object(handle, NULL, NULL, &buffer))) {
|
|
|
|
obj = (union acpi_object *)buffer.pointer;
|
|
|
|
if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
|
|
|
|
printk(IBM_ERR "Unknown BCLL data, "
|
|
|
|
"please report this to %s\n", IBM_MAIL);
|
|
|
|
rc = 0;
|
|
|
|
} else {
|
|
|
|
rc = obj->package.count;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(buffer.pointer);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static acpi_status __init brightness_find_bcll(acpi_handle handle, u32 lvl,
|
|
|
|
void *context, void **rv)
|
|
|
|
{
|
|
|
|
char name[ACPI_PATH_SEGMENT_LENGTH];
|
|
|
|
struct acpi_buffer buffer = { sizeof(name), &name };
|
|
|
|
|
|
|
|
if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
|
|
|
|
!strncmp("BCLL", name, sizeof(name) - 1)) {
|
|
|
|
if (tpacpi_query_bcll_levels(handle) == 16) {
|
|
|
|
*rv = handle;
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
} else {
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init brightness_check_levels(void)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
void *found_node = NULL;
|
|
|
|
|
|
|
|
if (!vid_handle) {
|
|
|
|
IBM_ACPIHANDLE_INIT(vid);
|
|
|
|
}
|
|
|
|
if (!vid_handle)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Search for a BCLL package with 16 levels */
|
|
|
|
status = acpi_walk_namespace(ACPI_TYPE_PACKAGE, vid_handle, 3,
|
|
|
|
brightness_find_bcll, NULL, &found_node);
|
|
|
|
|
|
|
|
return (ACPI_SUCCESS(status) && found_node != NULL);
|
|
|
|
}
|
|
|
|
|
2007-10-31 03:46:22 +08:00
|
|
|
static acpi_status __init brightness_find_bcl(acpi_handle handle, u32 lvl,
|
|
|
|
void *context, void **rv)
|
|
|
|
{
|
|
|
|
char name[ACPI_PATH_SEGMENT_LENGTH];
|
|
|
|
struct acpi_buffer buffer = { sizeof(name), &name };
|
|
|
|
|
|
|
|
if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)) &&
|
|
|
|
!strncmp("_BCL", name, sizeof(name) - 1)) {
|
|
|
|
*rv = handle;
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
} else {
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init brightness_check_std_acpi_support(void)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
void *found_node = NULL;
|
|
|
|
|
|
|
|
if (!vid_handle) {
|
|
|
|
IBM_ACPIHANDLE_INIT(vid);
|
|
|
|
}
|
|
|
|
if (!vid_handle)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Search for a _BCL method, but don't execute it */
|
|
|
|
status = acpi_walk_namespace(ACPI_TYPE_METHOD, vid_handle, 3,
|
|
|
|
brightness_find_bcl, NULL, &found_node);
|
|
|
|
|
|
|
|
return (ACPI_SUCCESS(status) && found_node != NULL);
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init brightness_init(struct ibm_init_struct *iibm)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
|
|
|
int b;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
|
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
mutex_init(&brightness_mutex);
|
|
|
|
|
2007-10-31 04:02:07 +08:00
|
|
|
if (!brightness_enable) {
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"brightness support disabled by module parameter\n");
|
|
|
|
return 1;
|
2007-10-31 03:46:22 +08:00
|
|
|
} else if (brightness_enable > 1) {
|
|
|
|
if (brightness_check_std_acpi_support()) {
|
|
|
|
printk(IBM_NOTICE
|
|
|
|
"standard ACPI backlight interface available, not loading native one...\n");
|
|
|
|
return 1;
|
|
|
|
}
|
2007-10-31 04:02:07 +08:00
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:43 +08:00
|
|
|
if (!brightness_mode) {
|
|
|
|
if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO)
|
|
|
|
brightness_mode = 2;
|
|
|
|
else
|
|
|
|
brightness_mode = 3;
|
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n",
|
|
|
|
brightness_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (brightness_mode > 3)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-10-31 03:46:20 +08:00
|
|
|
tp_features.bright_16levels =
|
|
|
|
thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO &&
|
|
|
|
brightness_check_levels();
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
b = brightness_get(NULL);
|
|
|
|
if (b < 0)
|
2007-07-19 10:45:43 +08:00
|
|
|
return 1;
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-10-31 03:46:20 +08:00
|
|
|
if (tp_features.bright_16levels)
|
|
|
|
printk(IBM_INFO "detected a 16-level brightness capable ThinkPad\n");
|
|
|
|
|
2007-04-24 22:48:20 +08:00
|
|
|
ibm_backlight_device = backlight_device_register(
|
|
|
|
TPACPI_BACKLIGHT_DEV_NAME, NULL, NULL,
|
|
|
|
&ibm_backlight_data);
|
2007-03-24 04:33:57 +08:00
|
|
|
if (IS_ERR(ibm_backlight_device)) {
|
|
|
|
printk(IBM_ERR "Could not register backlight device\n");
|
|
|
|
return PTR_ERR(ibm_backlight_device);
|
|
|
|
}
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-10-31 03:46:20 +08:00
|
|
|
ibm_backlight_device->props.max_brightness =
|
|
|
|
(tp_features.bright_16levels)? 15 : 7;
|
2007-03-24 04:33:57 +08:00
|
|
|
ibm_backlight_device->props.brightness = b;
|
|
|
|
backlight_update_status(ibm_backlight_device);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void brightness_exit(void)
|
|
|
|
{
|
|
|
|
if (ibm_backlight_device) {
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"calling backlight_device_unregister()\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
backlight_device_unregister(ibm_backlight_device);
|
|
|
|
ibm_backlight_device = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int brightness_update_status(struct backlight_device *bd)
|
|
|
|
{
|
2007-10-31 03:46:25 +08:00
|
|
|
/* it is the backlight class's job (caller) to handle
|
|
|
|
* EINTR and other errors properly */
|
2007-03-24 04:33:57 +08:00
|
|
|
return brightness_set(
|
|
|
|
(bd->props.fb_blank == FB_BLANK_UNBLANK &&
|
|
|
|
bd->props.power == FB_BLANK_UNBLANK) ?
|
|
|
|
bd->props.brightness : 0);
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:43 +08:00
|
|
|
/*
|
|
|
|
* ThinkPads can read brightness from two places: EC 0x31, or
|
|
|
|
* CMOS NVRAM byte 0x5E, bits 0-3.
|
|
|
|
*/
|
2006-10-21 05:30:28 +08:00
|
|
|
static int brightness_get(struct backlight_device *bd)
|
|
|
|
{
|
2007-07-19 10:45:43 +08:00
|
|
|
u8 lec = 0, lcmos = 0, level = 0;
|
2006-10-21 05:30:28 +08:00
|
|
|
|
2007-07-19 10:45:43 +08:00
|
|
|
if (brightness_mode & 1) {
|
|
|
|
if (!acpi_ec_read(brightness_offset, &lec))
|
|
|
|
return -EIO;
|
2007-10-31 03:46:20 +08:00
|
|
|
lec &= (tp_features.bright_16levels)? 0x0f : 0x07;
|
2007-07-19 10:45:43 +08:00
|
|
|
level = lec;
|
|
|
|
};
|
|
|
|
if (brightness_mode & 2) {
|
|
|
|
lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
|
|
|
|
& TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
|
|
|
|
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
|
2007-10-31 03:46:20 +08:00
|
|
|
lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
|
2007-07-19 10:45:43 +08:00
|
|
|
level = lcmos;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (brightness_mode == 3 && lec != lcmos) {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"CMOS NVRAM (%u) and EC (%u) do not agree "
|
|
|
|
"on display brightness level\n",
|
|
|
|
(unsigned int) lcmos,
|
|
|
|
(unsigned int) lec);
|
|
|
|
return -EIO;
|
|
|
|
}
|
2006-11-26 02:35:09 +08:00
|
|
|
|
2006-11-24 21:47:07 +08:00
|
|
|
return level;
|
2006-10-21 05:30:28 +08:00
|
|
|
}
|
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
/* May return EINTR which can always be mapped to ERESTARTSYS */
|
2006-10-21 05:30:28 +08:00
|
|
|
static int brightness_set(int value)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-07-19 10:45:48 +08:00
|
|
|
int cmos_cmd, inc, i, res;
|
|
|
|
int current_value;
|
2006-10-21 05:30:28 +08:00
|
|
|
|
2007-10-31 03:46:20 +08:00
|
|
|
if (value > ((tp_features.bright_16levels)? 15 : 7))
|
2007-07-19 10:45:43 +08:00
|
|
|
return -EINVAL;
|
2006-10-21 05:30:28 +08:00
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
res = mutex_lock_interruptible(&brightness_mutex);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
current_value = brightness_get(NULL);
|
|
|
|
if (current_value < 0) {
|
|
|
|
res = current_value;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:43 +08:00
|
|
|
cmos_cmd = value > current_value ?
|
|
|
|
TP_CMOS_BRIGHTNESS_UP :
|
|
|
|
TP_CMOS_BRIGHTNESS_DOWN;
|
2007-10-31 03:46:20 +08:00
|
|
|
inc = (value > current_value)? 1 : -1;
|
2007-07-19 10:45:43 +08:00
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
res = 0;
|
2006-10-21 05:30:28 +08:00
|
|
|
for (i = current_value; i != value; i += inc) {
|
2007-07-19 10:45:43 +08:00
|
|
|
if ((brightness_mode & 2) &&
|
2007-07-19 10:45:48 +08:00
|
|
|
issue_thinkpad_cmos_command(cmos_cmd)) {
|
|
|
|
res = -EIO;
|
|
|
|
goto errout;
|
|
|
|
}
|
2007-07-19 10:45:43 +08:00
|
|
|
if ((brightness_mode & 1) &&
|
2007-07-19 10:45:48 +08:00
|
|
|
!acpi_ec_write(brightness_offset, i + inc)) {
|
|
|
|
res = -EIO;
|
|
|
|
goto errout;;
|
|
|
|
}
|
2006-10-21 05:30:28 +08:00
|
|
|
}
|
|
|
|
|
2007-07-19 10:45:48 +08:00
|
|
|
errout:
|
|
|
|
mutex_unlock(&brightness_mutex);
|
|
|
|
return res;
|
2006-10-21 05:30:28 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static int brightness_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int level;
|
|
|
|
|
|
|
|
if ((level = brightness_get(NULL)) < 0) {
|
|
|
|
len += sprintf(p + len, "level:\t\tunreadable\n");
|
|
|
|
} else {
|
2007-10-31 03:46:20 +08:00
|
|
|
len += sprintf(p + len, "level:\t\t%d\n", level);
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "commands:\tup, down\n");
|
|
|
|
len += sprintf(p + len, "commands:\tlevel <level>"
|
2007-10-31 03:46:20 +08:00
|
|
|
" (<level> is 0-%d)\n",
|
|
|
|
(tp_features.bright_16levels) ? 15 : 7);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2006-10-21 05:30:28 +08:00
|
|
|
static int brightness_write(char *buf)
|
|
|
|
{
|
|
|
|
int level;
|
2007-10-31 03:46:25 +08:00
|
|
|
int rc;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *cmd;
|
2007-10-31 03:46:20 +08:00
|
|
|
int max_level = (tp_features.bright_16levels) ? 15 : 7;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
level = brightness_get(NULL);
|
|
|
|
if (level < 0)
|
|
|
|
return level;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
while ((cmd = next_cmd(&buf))) {
|
2005-08-17 12:00:00 +08:00
|
|
|
if (strlencmp(cmd, "up") == 0) {
|
2007-10-31 03:46:25 +08:00
|
|
|
if (level < max_level)
|
|
|
|
level++;
|
2005-08-17 12:00:00 +08:00
|
|
|
} else if (strlencmp(cmd, "down") == 0) {
|
2007-10-31 03:46:25 +08:00
|
|
|
if (level > 0)
|
|
|
|
level--;
|
|
|
|
} else if (sscanf(cmd, "level %d", &level) == 1 &&
|
|
|
|
level >= 0 && level <= max_level) {
|
|
|
|
/* new level set */
|
2005-08-17 12:00:00 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-10-31 03:46:25 +08:00
|
|
|
/*
|
|
|
|
* Now we know what the final level should be, so we try to set it.
|
|
|
|
* Doing it this way makes the syscall restartable in case of EINTR
|
|
|
|
*/
|
|
|
|
rc = brightness_set(level);
|
|
|
|
return (rc == -EINTR)? ERESTARTSYS : rc;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct brightness_driver_data = {
|
|
|
|
.name = "brightness",
|
|
|
|
.read = brightness_read,
|
|
|
|
.write = brightness_write,
|
|
|
|
.exit = brightness_exit,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*************************************************************************
|
|
|
|
* Volume subdriver
|
|
|
|
*/
|
2006-11-26 02:35:09 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
static int volume_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
u8 level;
|
|
|
|
|
|
|
|
if (!acpi_ec_read(volume_offset, &level)) {
|
|
|
|
len += sprintf(p + len, "level:\t\tunreadable\n");
|
|
|
|
} else {
|
|
|
|
len += sprintf(p + len, "level:\t\t%d\n", level & 0xf);
|
|
|
|
len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6));
|
|
|
|
len += sprintf(p + len, "commands:\tup, down, mute\n");
|
|
|
|
len += sprintf(p + len, "commands:\tlevel <level>"
|
|
|
|
" (<level> is 0-15)\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int volume_write(char *buf)
|
|
|
|
{
|
|
|
|
int cmos_cmd, inc, i;
|
|
|
|
u8 level, mute;
|
|
|
|
int new_level, new_mute;
|
|
|
|
char *cmd;
|
|
|
|
|
|
|
|
while ((cmd = next_cmd(&buf))) {
|
|
|
|
if (!acpi_ec_read(volume_offset, &level))
|
|
|
|
return -EIO;
|
|
|
|
new_mute = mute = level & 0x40;
|
|
|
|
new_level = level = level & 0xf;
|
|
|
|
|
|
|
|
if (strlencmp(cmd, "up") == 0) {
|
|
|
|
if (mute)
|
|
|
|
new_mute = 0;
|
|
|
|
else
|
|
|
|
new_level = level == 15 ? 15 : level + 1;
|
|
|
|
} else if (strlencmp(cmd, "down") == 0) {
|
|
|
|
if (mute)
|
|
|
|
new_mute = 0;
|
|
|
|
else
|
|
|
|
new_level = level == 0 ? 0 : level - 1;
|
|
|
|
} else if (sscanf(cmd, "level %d", &new_level) == 1 &&
|
|
|
|
new_level >= 0 && new_level <= 15) {
|
|
|
|
/* new_level set */
|
|
|
|
} else if (strlencmp(cmd, "mute") == 0) {
|
|
|
|
new_mute = 0x40;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (new_level != level) { /* mute doesn't change */
|
2007-03-24 04:33:57 +08:00
|
|
|
cmos_cmd = new_level > level ? TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN;
|
2005-08-17 12:00:00 +08:00
|
|
|
inc = new_level > level ? 1 : -1;
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
if (mute && (issue_thinkpad_cmos_command(cmos_cmd) ||
|
2005-08-17 12:00:00 +08:00
|
|
|
!acpi_ec_write(volume_offset, level)))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
for (i = level; i != new_level; i += inc)
|
2007-04-21 22:08:42 +08:00
|
|
|
if (issue_thinkpad_cmos_command(cmos_cmd) ||
|
2005-08-17 12:00:00 +08:00
|
|
|
!acpi_ec_write(volume_offset, i + inc))
|
|
|
|
return -EIO;
|
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
if (mute && (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) ||
|
2005-08-17 12:00:00 +08:00
|
|
|
!acpi_ec_write(volume_offset,
|
|
|
|
new_level + mute)))
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_mute != mute) { /* level doesn't change */
|
2007-03-24 04:33:57 +08:00
|
|
|
cmos_cmd = new_mute ? TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:42 +08:00
|
|
|
if (issue_thinkpad_cmos_command(cmos_cmd) ||
|
2005-08-17 12:00:00 +08:00
|
|
|
!acpi_ec_write(volume_offset, level + new_mute))
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct volume_driver_data = {
|
|
|
|
.name = "volume",
|
|
|
|
.read = volume_read,
|
|
|
|
.write = volume_write,
|
|
|
|
};
|
2007-03-24 04:33:57 +08:00
|
|
|
|
|
|
|
/*************************************************************************
|
|
|
|
* Fan subdriver
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FAN ACCESS MODES
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_RD_ACPI_GFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ACPI GFAN method: returns fan level
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* see TPACPI_FAN_WR_ACPI_SFAN
|
2007-04-21 22:08:29 +08:00
|
|
|
* EC 0x2f (HFSP) not available if GFAN exists
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_SFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
|
|
|
|
*
|
2007-04-21 22:08:29 +08:00
|
|
|
* EC 0x2f (HFSP) might be available *for reading*, but do not use
|
|
|
|
* it for writing.
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_TPEC:
|
2007-04-21 22:08:29 +08:00
|
|
|
* ThinkPad EC register 0x2f (HFSP): fan control loop mode
|
|
|
|
* Supported on almost all ThinkPads
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* Fan speed changes of any sort (including those caused by the
|
|
|
|
* disengaged mode) are usually done slowly by the firmware as the
|
|
|
|
* maximum ammount of fan duty cycle change per second seems to be
|
|
|
|
* limited.
|
|
|
|
*
|
|
|
|
* Reading is not available if GFAN exists.
|
|
|
|
* Writing is not available if SFAN exists.
|
|
|
|
*
|
|
|
|
* Bits
|
|
|
|
* 7 automatic mode engaged;
|
|
|
|
* (default operation mode of the ThinkPad)
|
|
|
|
* fan level is ignored in this mode.
|
2007-04-21 22:08:29 +08:00
|
|
|
* 6 full speed mode (takes precedence over bit 7);
|
2007-03-24 04:33:57 +08:00
|
|
|
* not available on all thinkpads. May disable
|
2007-04-21 22:08:29 +08:00
|
|
|
* the tachometer while the fan controller ramps up
|
|
|
|
* the speed (which can take up to a few *minutes*).
|
|
|
|
* Speeds up fan to 100% duty-cycle, which is far above
|
|
|
|
* the standard RPM levels. It is not impossible that
|
|
|
|
* it could cause hardware damage.
|
2007-03-24 04:33:57 +08:00
|
|
|
* 5-3 unused in some models. Extra bits for fan level
|
|
|
|
* in others, but still useless as all values above
|
|
|
|
* 7 map to the same speed as level 7 in these models.
|
|
|
|
* 2-0 fan level (0..7 usually)
|
|
|
|
* 0x00 = stop
|
|
|
|
* 0x07 = max (set when temperatures critical)
|
|
|
|
* Some ThinkPads may have other levels, see
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
|
|
|
|
* boot. Apparently the EC does not intialize it, so unless ACPI DSDT
|
|
|
|
* does so, its initial value is meaningless (0x07).
|
|
|
|
*
|
|
|
|
* For firmware bugs, refer to:
|
|
|
|
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
|
|
|
|
*
|
|
|
|
* ----
|
|
|
|
*
|
|
|
|
* ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
|
|
|
|
* Main fan tachometer reading (in RPM)
|
|
|
|
*
|
|
|
|
* This register is present on all ThinkPads with a new-style EC, and
|
|
|
|
* it is known not to be present on the A21m/e, and T22, as there is
|
|
|
|
* something else in offset 0x84 according to the ACPI DSDT. Other
|
|
|
|
* ThinkPads from this same time period (and earlier) probably lack the
|
|
|
|
* tachometer as well.
|
|
|
|
*
|
|
|
|
* Unfortunately a lot of ThinkPads with new-style ECs but whose firwmare
|
|
|
|
* was never fixed by IBM to report the EC firmware version string
|
|
|
|
* probably support the tachometer (like the early X models), so
|
|
|
|
* detecting it is quite hard. We need more data to know for sure.
|
|
|
|
*
|
|
|
|
* FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
|
|
|
|
* might result.
|
|
|
|
*
|
2007-04-21 22:08:29 +08:00
|
|
|
* FIRMWARE BUG: may go stale while the EC is switching to full speed
|
|
|
|
* mode.
|
2007-03-24 04:33:57 +08:00
|
|
|
*
|
|
|
|
* For firmware bugs, refer to:
|
|
|
|
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_ACPI_FANS:
|
2007-03-24 04:33:57 +08:00
|
|
|
* ThinkPad X31, X40, X41. Not available in the X60.
|
|
|
|
*
|
|
|
|
* FANS ACPI handle: takes three arguments: low speed, medium speed,
|
|
|
|
* high speed. ACPI DSDT seems to map these three speeds to levels
|
|
|
|
* as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
|
|
|
|
* (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
|
|
|
|
*
|
|
|
|
* The speeds are stored on handles
|
|
|
|
* (FANA:FAN9), (FANC:FANB), (FANE:FAND).
|
|
|
|
*
|
|
|
|
* There are three default speed sets, acessible as handles:
|
|
|
|
* FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
|
|
|
|
*
|
|
|
|
* ACPI DSDT switches which set is in use depending on various
|
|
|
|
* factors.
|
|
|
|
*
|
2007-04-21 22:08:28 +08:00
|
|
|
* TPACPI_FAN_WR_TPEC is also available and should be used to
|
2007-03-24 04:33:57 +08:00
|
|
|
* command the fan. The X31/X40/X41 seems to have 8 fan levels,
|
|
|
|
* but the ACPI tables just mention level 7.
|
|
|
|
*/
|
|
|
|
|
2006-11-24 21:47:09 +08:00
|
|
|
static enum fan_status_access_mode fan_status_access_mode;
|
|
|
|
static enum fan_control_access_mode fan_control_access_mode;
|
|
|
|
static enum fan_control_commands fan_control_commands;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
static u8 fan_control_initial_status;
|
2007-04-24 22:48:17 +08:00
|
|
|
static u8 fan_control_desired_level;
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
|
2006-12-08 17:43:41 +08:00
|
|
|
static void fan_watchdog_fire(struct work_struct *ignored);
|
2006-11-24 21:47:14 +08:00
|
|
|
static int fan_watchdog_maxinterval;
|
2006-12-08 17:43:41 +08:00
|
|
|
static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
IBM_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
|
|
|
|
IBM_HANDLE(gfan, ec, "GFAN", /* 570 */
|
|
|
|
"\\FSPD", /* 600e/x, 770e, 770x */
|
|
|
|
); /* all others */
|
|
|
|
IBM_HANDLE(sfan, ec, "SFAN", /* 570 */
|
|
|
|
"JFNS", /* 770x-JL */
|
|
|
|
); /* all others */
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
/*
|
|
|
|
* SYSFS fan layout: hwmon compatible (device)
|
|
|
|
*
|
|
|
|
* pwm*_enable:
|
|
|
|
* 0: "disengaged" mode
|
|
|
|
* 1: manual mode
|
|
|
|
* 2: native EC "auto" mode (recommended, hardware default)
|
|
|
|
*
|
|
|
|
* pwm*: set speed in manual mode, ignored otherwise.
|
|
|
|
* 0 is level 0; 255 is level 7. Intermediate points done with linear
|
|
|
|
* interpolation.
|
|
|
|
*
|
|
|
|
* fan*_input: tachometer reading, RPM
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* SYSFS fan layout: extensions
|
|
|
|
*
|
|
|
|
* fan_watchdog (driver):
|
|
|
|
* fan watchdog interval in seconds, 0 disables (default), max 120
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* sysfs fan pwm1_enable ----------------------------------------------- */
|
|
|
|
static ssize_t fan_pwm1_enable_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res, mode;
|
|
|
|
u8 status;
|
|
|
|
|
|
|
|
res = fan_get_status_safe(&status);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
if (unlikely(tp_features.fan_ctrl_status_undef)) {
|
|
|
|
if (status != fan_control_initial_status) {
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
} else {
|
|
|
|
/* Return most likely status. In fact, it
|
|
|
|
* might be the only possible status */
|
|
|
|
status = TP_EC_FAN_AUTO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & TP_EC_FAN_FULLSPEED) {
|
|
|
|
mode = 0;
|
|
|
|
} else if (status & TP_EC_FAN_AUTO) {
|
|
|
|
mode = 2;
|
|
|
|
} else
|
|
|
|
mode = 1;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t fan_pwm1_enable_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
int res, level;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 2, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
switch (t) {
|
|
|
|
case 0:
|
|
|
|
level = TP_EC_FAN_FULLSPEED;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
level = TPACPI_FAN_LAST_LEVEL;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
level = TP_EC_FAN_AUTO;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
/* reserved for software-controlled auto mode */
|
|
|
|
return -ENOSYS;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = fan_set_level_safe(level);
|
2007-04-28 09:00:12 +08:00
|
|
|
if (res == -ENXIO)
|
|
|
|
return -EINVAL;
|
|
|
|
else if (res < 0)
|
2007-04-24 22:48:17 +08:00
|
|
|
return res;
|
|
|
|
|
|
|
|
fan_watchdog_reset();
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_fan_pwm1_enable =
|
|
|
|
__ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
|
|
|
|
fan_pwm1_enable_show, fan_pwm1_enable_store);
|
|
|
|
|
|
|
|
/* sysfs fan pwm1 ------------------------------------------------------ */
|
|
|
|
static ssize_t fan_pwm1_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
u8 status;
|
|
|
|
|
|
|
|
res = fan_get_status_safe(&status);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
if (unlikely(tp_features.fan_ctrl_status_undef)) {
|
|
|
|
if (status != fan_control_initial_status) {
|
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
|
|
|
} else {
|
|
|
|
status = TP_EC_FAN_AUTO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((status &
|
|
|
|
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
|
|
|
|
status = fan_control_desired_level;
|
|
|
|
|
|
|
|
if (status > 7)
|
|
|
|
status = 7;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t fan_pwm1_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long s;
|
|
|
|
int rc;
|
|
|
|
u8 status, newlevel;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 255, &s))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* scale down from 0-255 to 0-7 */
|
|
|
|
newlevel = (s >> 5) & 0x07;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
|
|
|
rc = fan_get_status(&status);
|
|
|
|
if (!rc && (status &
|
|
|
|
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
|
|
|
|
rc = fan_set_level(newlevel);
|
2007-04-28 09:00:12 +08:00
|
|
|
if (rc == -ENXIO)
|
|
|
|
rc = -EINVAL;
|
|
|
|
else if (!rc) {
|
2007-04-24 22:48:17 +08:00
|
|
|
fan_update_desired_level(newlevel);
|
2007-04-28 09:00:11 +08:00
|
|
|
fan_watchdog_reset();
|
|
|
|
}
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return (rc)? rc : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_fan_pwm1 =
|
|
|
|
__ATTR(pwm1, S_IWUSR | S_IRUGO,
|
|
|
|
fan_pwm1_show, fan_pwm1_store);
|
|
|
|
|
|
|
|
/* sysfs fan fan1_input ------------------------------------------------ */
|
|
|
|
static ssize_t fan_fan1_input_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
unsigned int speed;
|
|
|
|
|
|
|
|
res = fan_get_speed(&speed);
|
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_fan_fan1_input =
|
|
|
|
__ATTR(fan1_input, S_IRUGO,
|
|
|
|
fan_fan1_input_show, NULL);
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
|
2007-04-24 22:48:17 +08:00
|
|
|
static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
unsigned long t;
|
|
|
|
|
|
|
|
if (parse_strtoul(buf, 120, &t))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
fan_watchdog_maxinterval = t;
|
|
|
|
fan_watchdog_reset();
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
|
|
|
|
fan_fan_watchdog_show, fan_fan_watchdog_store);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
static struct attribute *fan_attributes[] = {
|
|
|
|
&dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
|
|
|
|
&dev_attr_fan_fan1_input.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group fan_attr_group = {
|
|
|
|
.attrs = fan_attributes,
|
|
|
|
};
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init fan_init(struct ibm_init_struct *iibm)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-24 22:48:17 +08:00
|
|
|
int rc;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "initializing fan subdriver\n");
|
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_init(&fan_mutex);
|
2007-04-21 22:08:28 +08:00
|
|
|
fan_status_access_mode = TPACPI_FAN_NONE;
|
|
|
|
fan_control_access_mode = TPACPI_FAN_WR_NONE;
|
2006-11-24 21:47:09 +08:00
|
|
|
fan_control_commands = 0;
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_watchdog_maxinterval = 0;
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
2007-04-24 22:48:17 +08:00
|
|
|
fan_control_desired_level = 7;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(fans);
|
|
|
|
IBM_ACPIHANDLE_INIT(gfan);
|
|
|
|
IBM_ACPIHANDLE_INIT(sfan);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (gfan_handle) {
|
|
|
|
/* 570, 600e/x, 770e, 770x */
|
2007-04-21 22:08:28 +08:00
|
|
|
fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
|
2006-11-24 21:47:09 +08:00
|
|
|
} else {
|
|
|
|
/* all other ThinkPads: note that even old-style
|
|
|
|
* ThinkPad ECs supports the fan control register */
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
if (likely(acpi_ec_read(fan_status_offset,
|
|
|
|
&fan_control_initial_status))) {
|
2007-04-21 22:08:28 +08:00
|
|
|
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
|
|
|
|
/* In some ThinkPads, neither the EC nor the ACPI
|
|
|
|
* DSDT initialize the fan status, and it ends up
|
|
|
|
* being set to 0x07 when it *could* be either
|
|
|
|
* 0x07 or 0x80.
|
|
|
|
*
|
|
|
|
* Enable for TP-1Y (T43), TP-78 (R51e),
|
|
|
|
* TP-76 (R52), TP-70 (T43, R52), which are known
|
|
|
|
* to be buggy. */
|
2007-07-19 10:45:42 +08:00
|
|
|
if (fan_control_initial_status == 0x07) {
|
|
|
|
switch (thinkpad_id.ec_model) {
|
|
|
|
case 0x5931: /* TP-1Y */
|
|
|
|
case 0x3837: /* TP-78 */
|
|
|
|
case 0x3637: /* TP-76 */
|
|
|
|
case 0x3037: /* TP-70 */
|
|
|
|
printk(IBM_NOTICE
|
|
|
|
"fan_init: initial fan status is "
|
|
|
|
"unknown, assuming it is in auto "
|
|
|
|
"mode\n");
|
|
|
|
tp_features.fan_ctrl_status_undef = 1;
|
|
|
|
;;
|
|
|
|
}
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
}
|
2006-11-24 21:47:09 +08:00
|
|
|
} else {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"ThinkPad ACPI EC access misbehaving, "
|
|
|
|
"fan status and control unavailable\n");
|
2007-04-21 22:08:31 +08:00
|
|
|
return 1;
|
2006-11-24 21:47:09 +08:00
|
|
|
}
|
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:09 +08:00
|
|
|
if (sfan_handle) {
|
|
|
|
/* 570, 770x-JL */
|
2007-04-21 22:08:28 +08:00
|
|
|
fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
|
2006-11-24 21:47:12 +08:00
|
|
|
fan_control_commands |=
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
|
2005-08-17 12:00:00 +08:00
|
|
|
} else {
|
2006-11-24 21:47:09 +08:00
|
|
|
if (!gfan_handle) {
|
|
|
|
/* gfan without sfan means no fan control */
|
|
|
|
/* all other models implement TP EC 0x2f control */
|
|
|
|
|
|
|
|
if (fans_handle) {
|
2006-11-24 21:47:11 +08:00
|
|
|
/* X31, X40, X41 */
|
2006-11-24 21:47:09 +08:00
|
|
|
fan_control_access_mode =
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_FAN_WR_ACPI_FANS;
|
2006-11-24 21:47:09 +08:00
|
|
|
fan_control_commands |=
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_FAN_CMD_SPEED |
|
|
|
|
TPACPI_FAN_CMD_LEVEL |
|
|
|
|
TPACPI_FAN_CMD_ENABLE;
|
2006-11-24 21:47:09 +08:00
|
|
|
} else {
|
2007-04-21 22:08:28 +08:00
|
|
|
fan_control_access_mode = TPACPI_FAN_WR_TPEC;
|
2006-11-24 21:47:13 +08:00
|
|
|
fan_control_commands |=
|
2007-04-21 22:08:28 +08:00
|
|
|
TPACPI_FAN_CMD_LEVEL |
|
|
|
|
TPACPI_FAN_CMD_ENABLE;
|
2006-11-24 21:47:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_INIT, "fan is %s, modes %d, %d\n",
|
|
|
|
str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
|
|
|
|
fan_control_access_mode != TPACPI_FAN_WR_NONE),
|
|
|
|
fan_status_access_mode, fan_control_access_mode);
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
/* fan control master switch */
|
|
|
|
if (!fan_control_allowed) {
|
|
|
|
fan_control_access_mode = TPACPI_FAN_WR_NONE;
|
|
|
|
fan_control_commands = 0;
|
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"fan control features disabled by parameter\n");
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
/* update fan_control_desired_level */
|
|
|
|
if (fan_status_access_mode != TPACPI_FAN_NONE)
|
|
|
|
fan_get_status_safe(NULL);
|
|
|
|
|
|
|
|
if (fan_status_access_mode != TPACPI_FAN_NONE ||
|
|
|
|
fan_control_access_mode != TPACPI_FAN_WR_NONE) {
|
2007-09-25 17:38:03 +08:00
|
|
|
rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
2007-04-24 22:48:17 +08:00
|
|
|
&fan_attr_group);
|
|
|
|
if (!(rc < 0))
|
2007-09-25 17:38:03 +08:00
|
|
|
rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
|
2007-04-24 22:48:17 +08:00
|
|
|
&driver_attr_fan_watchdog);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
return 0;
|
|
|
|
} else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Call with fan_mutex held
|
|
|
|
*/
|
|
|
|
static void fan_update_desired_level(u8 status)
|
|
|
|
{
|
|
|
|
if ((status &
|
|
|
|
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
|
|
|
|
if (status > 7)
|
|
|
|
fan_control_desired_level = 7;
|
|
|
|
else
|
|
|
|
fan_control_desired_level = status;
|
|
|
|
}
|
2006-11-24 21:47:09 +08:00
|
|
|
}
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
static int fan_get_status(u8 *status)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2006-11-24 21:47:10 +08:00
|
|
|
u8 s;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
/* TODO:
|
2007-04-21 22:08:28 +08:00
|
|
|
* Add TPACPI_FAN_RD_ACPI_FANS ? */
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
switch (fan_status_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_ACPI_GFAN:
|
2005-08-17 12:00:00 +08:00
|
|
|
/* 570, 600e/x, 770e, 770x */
|
2006-11-24 21:47:10 +08:00
|
|
|
|
|
|
|
if (unlikely(!acpi_evalf(gfan_handle, &s, NULL, "d")))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
if (likely(status))
|
|
|
|
*status = s & 0x07;
|
2006-11-24 21:47:10 +08:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_TPEC:
|
2005-08-17 12:00:00 +08:00
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
2006-11-24 21:47:10 +08:00
|
|
|
if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
|
2006-11-24 21:47:10 +08:00
|
|
|
return -EIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
if (likely(status))
|
|
|
|
*status = s;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -ENXIO;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
static int fan_get_status_safe(u8 *status)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 s;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:17 +08:00
|
|
|
rc = fan_get_status(&s);
|
|
|
|
if (!rc)
|
|
|
|
fan_update_desired_level(s);
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
*status = s;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void fan_exit(void)
|
|
|
|
{
|
2007-04-21 22:08:44 +08:00
|
|
|
vdbg_printk(TPACPI_DBG_EXIT, "cancelling any pending fan watchdog tasks\n");
|
2007-04-24 22:48:17 +08:00
|
|
|
|
|
|
|
/* FIXME: can we really do this unconditionally? */
|
2007-09-25 17:38:03 +08:00
|
|
|
sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
|
|
|
|
driver_remove_file(&tpacpi_hwmon_pdriver.driver, &driver_attr_fan_watchdog);
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
cancel_delayed_work(&fan_watchdog_task);
|
|
|
|
flush_scheduled_work();
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
static int fan_get_speed(unsigned int *speed)
|
|
|
|
{
|
|
|
|
u8 hi, lo;
|
|
|
|
|
|
|
|
switch (fan_status_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_TPEC:
|
2005-08-17 12:00:00 +08:00
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
2006-11-24 21:47:10 +08:00
|
|
|
if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
|
|
|
|
!acpi_ec_read(fan_rpm_offset + 1, &hi)))
|
|
|
|
return -EIO;
|
|
|
|
|
2006-11-24 21:47:10 +08:00
|
|
|
if (likely(speed))
|
|
|
|
*speed = (hi << 8) | lo;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static void fan_watchdog_fire(struct work_struct *ignored)
|
2006-11-24 21:47:14 +08:00
|
|
|
{
|
2007-04-21 22:08:44 +08:00
|
|
|
int rc;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
|
|
|
|
return;
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
printk(IBM_NOTICE "fan watchdog: enabling fan\n");
|
2007-04-21 22:08:44 +08:00
|
|
|
rc = fan_set_enable();
|
|
|
|
if (rc < 0) {
|
|
|
|
printk(IBM_ERR "fan watchdog: error %d while enabling fan, "
|
|
|
|
"will try again later...\n", -rc);
|
2007-03-24 04:33:57 +08:00
|
|
|
/* reschedule for later */
|
|
|
|
fan_watchdog_reset();
|
|
|
|
}
|
2006-11-24 21:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fan_watchdog_reset(void)
|
|
|
|
{
|
2007-07-19 10:45:27 +08:00
|
|
|
static int fan_watchdog_active;
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2007-04-28 09:00:10 +08:00
|
|
|
if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
|
|
|
|
return;
|
|
|
|
|
2006-11-24 21:47:14 +08:00
|
|
|
if (fan_watchdog_active)
|
|
|
|
cancel_delayed_work(&fan_watchdog_task);
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
if (fan_watchdog_maxinterval > 0 &&
|
|
|
|
tpacpi_lifecycle != TPACPI_LIFE_EXITING) {
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_watchdog_active = 1;
|
|
|
|
if (!schedule_delayed_work(&fan_watchdog_task,
|
|
|
|
msecs_to_jiffies(fan_watchdog_maxinterval
|
|
|
|
* 1000))) {
|
|
|
|
printk(IBM_ERR "failed to schedule the fan watchdog, "
|
|
|
|
"watchdog will not trigger\n");
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
fan_watchdog_active = 0;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_set_level(int level)
|
2005-08-17 12:00:00 +08:00
|
|
|
{
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2006-11-24 21:47:11 +08:00
|
|
|
if (level >= 0 && level <= 7) {
|
2007-04-24 22:48:17 +08:00
|
|
|
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
|
2005-08-17 12:00:00 +08:00
|
|
|
return -EIO;
|
2006-11-24 21:47:11 +08:00
|
|
|
} else
|
|
|
|
return -EINVAL;
|
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
|
|
|
case TPACPI_FAN_WR_TPEC:
|
|
|
|
if ((level != TP_EC_FAN_AUTO) &&
|
|
|
|
(level != TP_EC_FAN_FULLSPEED) &&
|
2006-11-24 21:47:13 +08:00
|
|
|
((level < 0) || (level > 7)))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-24 22:48:18 +08:00
|
|
|
/* safety net should the EC not support AUTO
|
|
|
|
* or FULLSPEED mode bits and just ignore them */
|
|
|
|
if (level & TP_EC_FAN_FULLSPEED)
|
|
|
|
level |= 7; /* safety min speed 7 */
|
|
|
|
else if (level & TP_EC_FAN_FULLSPEED)
|
|
|
|
level |= 4; /* safety min speed 4 */
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
if (!acpi_ec_write(fan_status_offset, level))
|
2006-11-24 21:47:13 +08:00
|
|
|
return -EIO;
|
ACPI: ibm-acpi: workaround for EC 0x2f initialization bug
A few ThinkPads fail to initialize EC register 0x2f both in the EC
firmware and ACPI DSDT. If the BIOS and the ACPI DSDT also do not
initialize it, then the initial status of that register does not
correspond to reality.
On all reported buggy machines, EC 0x2f will read 0x07 (fan level 7) upon
cold boot, when the EC is actually in mode 0x80 (auto mode). Since
returning a text string ("unknown") would break a number of userspace
programs, instead we correct the reading for the most probably correct
answer, and return it is in auto mode.
The workaround flags the status and level as unknown on module load/kernel
boot, until we are certain at least one fan control command was issued,
either by us, or by something else.
We don't work around the bug by doing a "fan enable" at module
load/startup (which would initialize the EC register) because it is not
known if these ThinkPad ACPI DSDT might have set the fan to level 7
instead of "auto" (we don't know if they can do this or not) due to a
thermal condition, and we don't want to override that, should they be
capable of it.
We should be setting the workaround flag to "status known" upon resume, as
both reports and a exaustive search on the DSDT tables at acpi.sf.net show
that the DSDTs always enable the fan on resume, thus working around the
bug. But since we don't have suspend/resume handlers in ibm-acpi yet and
the "EC register 0x2f was modified" logic is likely to catch the change
anyway, we don't.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
2006-11-24 21:47:14 +08:00
|
|
|
else
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
2006-11-24 21:47:13 +08:00
|
|
|
break;
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
default:
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
static int fan_set_level_safe(int level)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:17 +08:00
|
|
|
|
|
|
|
if (level == TPACPI_FAN_LAST_LEVEL)
|
|
|
|
level = fan_control_desired_level;
|
|
|
|
|
|
|
|
rc = fan_set_level(level);
|
|
|
|
if (!rc)
|
|
|
|
fan_update_desired_level(level);
|
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_set_enable(void)
|
|
|
|
{
|
2006-11-24 21:47:12 +08:00
|
|
|
u8 s;
|
|
|
|
int rc;
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
|
|
|
case TPACPI_FAN_WR_TPEC:
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = fan_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
break;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
|
|
|
/* Don't go out of emergency fan mode */
|
2007-04-24 22:48:18 +08:00
|
|
|
if (s != 7) {
|
|
|
|
s &= 0x07;
|
|
|
|
s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
|
|
|
|
}
|
2006-11-24 21:47:12 +08:00
|
|
|
|
|
|
|
if (!acpi_ec_write(fan_status_offset, s))
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -EIO;
|
|
|
|
else {
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = 0;
|
|
|
|
}
|
2006-11-24 21:47:12 +08:00
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = fan_get_status(&s);
|
|
|
|
if (rc < 0)
|
|
|
|
break;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
|
|
|
s &= 0x07;
|
|
|
|
|
|
|
|
/* Set fan to at least level 4 */
|
2007-04-24 22:48:18 +08:00
|
|
|
s |= 4;
|
2006-11-24 21:47:12 +08:00
|
|
|
|
|
|
|
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
|
2007-04-24 22:48:15 +08:00
|
|
|
rc= -EIO;
|
|
|
|
else
|
|
|
|
rc = 0;
|
2006-11-24 21:47:11 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -ENXIO;
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
2007-04-24 22:48:15 +08:00
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_set_disable(void)
|
|
|
|
{
|
2007-04-24 22:48:15 +08:00
|
|
|
int rc;
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
|
|
|
rc = 0;
|
2006-11-24 21:47:11 +08:00
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
|
|
|
case TPACPI_FAN_WR_TPEC:
|
2006-11-24 21:47:11 +08:00
|
|
|
if (!acpi_ec_write(fan_status_offset, 0x00))
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -EIO;
|
2007-04-24 22:48:17 +08:00
|
|
|
else {
|
|
|
|
fan_control_desired_level = 0;
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
2007-04-24 22:48:17 +08:00
|
|
|
}
|
2006-11-24 21:47:11 +08:00
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2006-11-24 21:47:12 +08:00
|
|
|
if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -EIO;
|
2007-04-24 22:48:17 +08:00
|
|
|
else
|
|
|
|
fan_control_desired_level = 0;
|
2006-11-24 21:47:12 +08:00
|
|
|
break;
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
default:
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -ENXIO;
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
2007-04-24 22:48:15 +08:00
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
|
2007-04-24 22:48:15 +08:00
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_set_speed(int speed)
|
|
|
|
{
|
2007-04-24 22:48:15 +08:00
|
|
|
int rc;
|
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
if (!fan_control_allowed)
|
|
|
|
return -EPERM;
|
|
|
|
|
2007-10-31 03:46:24 +08:00
|
|
|
if (mutex_lock_interruptible(&fan_mutex))
|
|
|
|
return -ERESTARTSYS;
|
2007-04-24 22:48:15 +08:00
|
|
|
|
|
|
|
rc = 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_FANS:
|
2007-03-24 04:33:57 +08:00
|
|
|
if (speed >= 0 && speed <= 65535) {
|
|
|
|
if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
|
|
|
|
speed, speed, speed))
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -EIO;
|
2007-03-24 04:33:57 +08:00
|
|
|
} else
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -EINVAL;
|
2007-03-24 04:33:57 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2007-04-24 22:48:15 +08:00
|
|
|
rc = -ENXIO;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
2007-04-24 22:48:15 +08:00
|
|
|
|
|
|
|
mutex_unlock(&fan_mutex);
|
|
|
|
return rc;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_read(char *p)
|
|
|
|
{
|
|
|
|
int len = 0;
|
|
|
|
int rc;
|
|
|
|
u8 status;
|
|
|
|
unsigned int speed = 0;
|
|
|
|
|
|
|
|
switch (fan_status_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_ACPI_GFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
/* 570, 600e/x, 770e, 770x */
|
2007-04-24 22:48:17 +08:00
|
|
|
if ((rc = fan_get_status_safe(&status)) < 0)
|
2007-03-24 04:33:57 +08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
len += sprintf(p + len, "status:\t\t%s\n"
|
|
|
|
"level:\t\t%d\n",
|
|
|
|
(status != 0) ? "enabled" : "disabled", status);
|
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_RD_TPEC:
|
2007-03-24 04:33:57 +08:00
|
|
|
/* all except 570, 600e/x, 770e, 770x */
|
2007-04-24 22:48:17 +08:00
|
|
|
if ((rc = fan_get_status_safe(&status)) < 0)
|
2007-03-24 04:33:57 +08:00
|
|
|
return rc;
|
|
|
|
|
2007-04-21 22:08:36 +08:00
|
|
|
if (unlikely(tp_features.fan_ctrl_status_undef)) {
|
2007-03-24 04:33:57 +08:00
|
|
|
if (status != fan_control_initial_status)
|
2007-04-21 22:08:36 +08:00
|
|
|
tp_features.fan_ctrl_status_undef = 0;
|
2007-03-24 04:33:57 +08:00
|
|
|
else
|
|
|
|
/* Return most likely status. In fact, it
|
|
|
|
* might be the only possible status */
|
2007-04-21 22:08:28 +08:00
|
|
|
status = TP_EC_FAN_AUTO;
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
len += sprintf(p + len, "status:\t\t%s\n",
|
|
|
|
(status != 0) ? "enabled" : "disabled");
|
|
|
|
|
|
|
|
if ((rc = fan_get_speed(&speed)) < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
len += sprintf(p + len, "speed:\t\t%d\n", speed);
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (status & TP_EC_FAN_FULLSPEED)
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Disengaged mode takes precedence */
|
|
|
|
len += sprintf(p + len, "level:\t\tdisengaged\n");
|
2007-04-21 22:08:28 +08:00
|
|
|
else if (status & TP_EC_FAN_AUTO)
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "level:\t\tauto\n");
|
|
|
|
else
|
|
|
|
len += sprintf(p + len, "level:\t\t%d\n", status);
|
|
|
|
break;
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_NONE:
|
2007-03-24 04:33:57 +08:00
|
|
|
default:
|
|
|
|
len += sprintf(p + len, "status:\t\tnot supported\n");
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "commands:\tlevel <level>");
|
|
|
|
|
|
|
|
switch (fan_control_access_mode) {
|
2007-04-21 22:08:28 +08:00
|
|
|
case TPACPI_FAN_WR_ACPI_SFAN:
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, " (<level> is 0-7)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
len += sprintf(p + len, " (<level> is 0-7, "
|
2007-04-24 22:48:17 +08:00
|
|
|
"auto, disengaged, full-speed)\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2006-11-24 21:47:11 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "commands:\tenable, disable\n"
|
|
|
|
"commands:\twatchdog <timeout> (<timeout> is 0 (off), "
|
|
|
|
"1-120 (seconds))\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:28 +08:00
|
|
|
if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
|
2007-03-24 04:33:57 +08:00
|
|
|
len += sprintf(p + len, "commands:\tspeed <speed>"
|
|
|
|
" (<speed> is 0-65535)\n");
|
|
|
|
|
|
|
|
return len;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_write_cmd_level(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int level;
|
|
|
|
|
2006-11-24 21:47:13 +08:00
|
|
|
if (strlencmp(cmd, "level auto") == 0)
|
2007-04-21 22:08:28 +08:00
|
|
|
level = TP_EC_FAN_AUTO;
|
2007-04-24 22:48:17 +08:00
|
|
|
else if ((strlencmp(cmd, "level disengaged") == 0) |
|
|
|
|
(strlencmp(cmd, "level full-speed") == 0))
|
2007-04-21 22:08:28 +08:00
|
|
|
level = TP_EC_FAN_FULLSPEED;
|
2006-11-24 21:47:13 +08:00
|
|
|
else if (sscanf(cmd, "level %d", &level) != 1)
|
2006-11-24 21:47:11 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-04-24 22:48:17 +08:00
|
|
|
if ((*rc = fan_set_level_safe(level)) == -ENXIO)
|
2006-11-24 21:47:11 +08:00
|
|
|
printk(IBM_ERR "level command accepted for unsupported "
|
|
|
|
"access mode %d", fan_control_access_mode);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_enable(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
if (strlencmp(cmd, "enable") != 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((*rc = fan_set_enable()) == -ENXIO)
|
|
|
|
printk(IBM_ERR "enable command accepted for unsupported "
|
|
|
|
"access mode %d", fan_control_access_mode);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_disable(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
if (strlencmp(cmd, "disable") != 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((*rc = fan_set_disable()) == -ENXIO)
|
|
|
|
printk(IBM_ERR "disable command accepted for unsupported "
|
|
|
|
"access mode %d", fan_control_access_mode);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fan_write_cmd_speed(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int speed;
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
/* TODO:
|
|
|
|
* Support speed <low> <medium> <high> ? */
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
if (sscanf(cmd, "speed %d", &speed) != 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((*rc = fan_set_speed(speed)) == -ENXIO)
|
|
|
|
printk(IBM_ERR "speed command accepted for unsupported "
|
|
|
|
"access mode %d", fan_control_access_mode);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:14 +08:00
|
|
|
static int fan_write_cmd_watchdog(const char *cmd, int *rc)
|
|
|
|
{
|
|
|
|
int interval;
|
|
|
|
|
|
|
|
if (sscanf(cmd, "watchdog %d", &interval) != 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (interval < 0 || interval > 120)
|
|
|
|
*rc = -EINVAL;
|
|
|
|
else
|
|
|
|
fan_watchdog_maxinterval = interval;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-11-24 21:47:11 +08:00
|
|
|
static int fan_write(char *buf)
|
|
|
|
{
|
|
|
|
char *cmd;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
while (!rc && (cmd = next_cmd(&buf))) {
|
2007-04-21 22:08:28 +08:00
|
|
|
if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
fan_write_cmd_level(cmd, &rc)) &&
|
2007-04-21 22:08:28 +08:00
|
|
|
!((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
(fan_write_cmd_enable(cmd, &rc) ||
|
2006-11-24 21:47:14 +08:00
|
|
|
fan_write_cmd_disable(cmd, &rc) ||
|
|
|
|
fan_write_cmd_watchdog(cmd, &rc))) &&
|
2007-04-21 22:08:28 +08:00
|
|
|
!((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
|
2006-11-24 21:47:11 +08:00
|
|
|
fan_write_cmd_speed(cmd, &rc))
|
|
|
|
)
|
|
|
|
rc = -EINVAL;
|
2006-11-24 21:47:14 +08:00
|
|
|
else if (!rc)
|
|
|
|
fan_watchdog_reset();
|
2006-11-24 21:47:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_struct fan_driver_data = {
|
|
|
|
.name = "fan",
|
|
|
|
.read = fan_read,
|
|
|
|
.write = fan_write,
|
|
|
|
.exit = fan_exit,
|
|
|
|
};
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/****************************************************************************
|
|
|
|
****************************************************************************
|
|
|
|
*
|
|
|
|
* Infrastructure
|
|
|
|
*
|
|
|
|
****************************************************************************
|
|
|
|
****************************************************************************/
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
/* sysfs name ---------------------------------------------------------- */
|
|
|
|
static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", IBM_NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
|
|
|
|
__ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL);
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* /proc support */
|
2007-07-19 10:45:27 +08:00
|
|
|
static struct proc_dir_entry *proc_dir;
|
2006-11-24 21:47:14 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Subdriver registry */
|
2007-04-21 22:08:33 +08:00
|
|
|
static LIST_HEAD(tpacpi_all_drivers);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*
|
|
|
|
* Module and infrastructure proble, init and exit handling
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DEBUG
|
2007-04-21 22:08:33 +08:00
|
|
|
static const char * __init str_supported(int is_supported)
|
2007-04-21 22:08:32 +08:00
|
|
|
{
|
2007-04-21 22:08:33 +08:00
|
|
|
static char text_unsupported[] __initdata = "not supported";
|
2007-04-21 22:08:32 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
|
2007-04-21 22:08:32 +08:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static int __init ibm_init(struct ibm_init_struct *iibm)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret;
|
2007-04-21 22:08:33 +08:00
|
|
|
struct ibm_struct *ibm = iibm->data;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct proc_dir_entry *entry;
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
BUG_ON(ibm == NULL);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&ibm->all_drivers);
|
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
if (ibm->flags.experimental && !experimental)
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"probing for %s\n", ibm->name);
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
if (iibm->init) {
|
|
|
|
ret = iibm->init(iibm);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (ret > 0)
|
|
|
|
return 0; /* probe failed */
|
|
|
|
if (ret)
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
2007-04-21 22:08:33 +08:00
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.init_called = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->acpi) {
|
|
|
|
if (ibm->acpi->hid) {
|
|
|
|
ret = register_tpacpi_subdriver(ibm);
|
|
|
|
if (ret)
|
|
|
|
goto err_out;
|
|
|
|
}
|
2007-04-21 22:08:31 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->acpi->notify) {
|
|
|
|
ret = setup_acpi_notify(ibm);
|
|
|
|
if (ret == -ENODEV) {
|
|
|
|
printk(IBM_NOTICE "disabling subdriver %s\n",
|
|
|
|
ibm->name);
|
|
|
|
ret = 0;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_out;
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"%s installed\n", ibm->name);
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ibm->read) {
|
|
|
|
entry = create_proc_entry(ibm->name,
|
|
|
|
S_IFREG | S_IRUGO | S_IWUSR,
|
|
|
|
proc_dir);
|
|
|
|
if (!entry) {
|
|
|
|
printk(IBM_ERR "unable to create proc entry %s\n",
|
|
|
|
ibm->name);
|
2007-04-21 22:08:31 +08:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_out;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
|
|
|
entry->owner = THIS_MODULE;
|
|
|
|
entry->data = ibm;
|
2007-04-21 22:08:37 +08:00
|
|
|
entry->read_proc = &dispatch_procfs_read;
|
2005-08-17 12:00:00 +08:00
|
|
|
if (ibm->write)
|
2007-04-21 22:08:37 +08:00
|
|
|
entry->write_proc = &dispatch_procfs_write;
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.proc_created = 1;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2007-04-21 22:08:31 +08:00
|
|
|
|
|
|
|
err_out:
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT,
|
|
|
|
"%s: at error exit path with result %d\n",
|
|
|
|
ibm->name, ret);
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
ibm_exit(ibm);
|
|
|
|
return (ret < 0)? ret : 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ibm_exit(struct ibm_struct *ibm)
|
|
|
|
{
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
|
2007-04-21 22:08:33 +08:00
|
|
|
|
|
|
|
list_del_init(&ibm->all_drivers);
|
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->flags.acpi_notify_installed) {
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"%s: acpi_remove_notify_handler\n", ibm->name);
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
acpi_remove_notify_handler(*ibm->acpi->handle,
|
|
|
|
ibm->acpi->type,
|
|
|
|
dispatch_acpi_notify);
|
|
|
|
ibm->flags.acpi_notify_installed = 0;
|
|
|
|
ibm->flags.acpi_notify_installed = 0;
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
if (ibm->flags.proc_created) {
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"%s: remove_proc_entry\n", ibm->name);
|
2005-04-17 06:20:36 +08:00
|
|
|
remove_proc_entry(ibm->name, proc_dir);
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.proc_created = 0;
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
if (ibm->flags.acpi_driver_registered) {
|
2007-04-21 22:08:32 +08:00
|
|
|
dbg_printk(TPACPI_DBG_EXIT,
|
|
|
|
"%s: acpi_bus_unregister_driver\n", ibm->name);
|
2007-04-21 22:08:37 +08:00
|
|
|
BUG_ON(!ibm->acpi);
|
|
|
|
acpi_bus_unregister_driver(ibm->acpi->driver);
|
|
|
|
kfree(ibm->acpi->driver);
|
|
|
|
ibm->acpi->driver = NULL;
|
|
|
|
ibm->flags.acpi_driver_registered = 0;
|
2007-04-21 22:08:31 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:35 +08:00
|
|
|
if (ibm->flags.init_called && ibm->exit) {
|
2007-04-21 22:08:31 +08:00
|
|
|
ibm->exit();
|
2007-04-21 22:08:35 +08:00
|
|
|
ibm->flags.init_called = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-04-21 22:08:33 +08:00
|
|
|
|
|
|
|
dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Probing */
|
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-10-04 03:15:40 +08:00
|
|
|
const struct dmi_device *dev = NULL;
|
2007-03-24 04:33:57 +08:00
|
|
|
char ec_fw_string[18];
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
if (!tp)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memset(tp, 0, sizeof(*tp));
|
|
|
|
|
|
|
|
if (dmi_name_in_vendors("IBM"))
|
|
|
|
tp->vendor = PCI_VENDOR_ID_IBM;
|
|
|
|
else if (dmi_name_in_vendors("LENOVO"))
|
|
|
|
tp->vendor = PCI_VENDOR_ID_LENOVO;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
tp->bios_version_str = kstrdup(dmi_get_system_info(DMI_BIOS_VERSION),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!tp->bios_version_str)
|
|
|
|
return;
|
|
|
|
tp->bios_model = tp->bios_version_str[0]
|
|
|
|
| (tp->bios_version_str[1] << 8);
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/*
|
|
|
|
* ThinkPad T23 or newer, A31 or newer, R50e or newer,
|
|
|
|
* X32 or newer, all Z series; Some models must have an
|
|
|
|
* up-to-date BIOS or they will not be detected.
|
|
|
|
*
|
|
|
|
* See http://thinkwiki.org/wiki/List_of_DMI_IDs
|
|
|
|
*/
|
|
|
|
while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
|
|
|
|
if (sscanf(dev->name,
|
|
|
|
"IBM ThinkPad Embedded Controller -[%17c",
|
|
|
|
ec_fw_string) == 1) {
|
|
|
|
ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
|
|
|
|
ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
|
2007-07-19 10:45:42 +08:00
|
|
|
|
|
|
|
tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
|
|
|
|
tp->ec_model = ec_fw_string[0]
|
|
|
|
| (ec_fw_string[1] << 8);
|
|
|
|
break;
|
2005-08-17 12:00:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-07-19 10:45:42 +08:00
|
|
|
|
|
|
|
tp->model_str = kstrdup(dmi_get_system_info(DMI_PRODUCT_VERSION),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (strnicmp(tp->model_str, "ThinkPad", 8) != 0) {
|
|
|
|
kfree(tp->model_str);
|
|
|
|
tp->model_str = NULL;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
static int __init probe_for_thinkpad(void)
|
|
|
|
{
|
|
|
|
int is_thinkpad;
|
|
|
|
|
|
|
|
if (acpi_disabled)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Non-ancient models have better DMI tagging, but very old models
|
|
|
|
* don't.
|
|
|
|
*/
|
2007-07-19 10:45:42 +08:00
|
|
|
is_thinkpad = (thinkpad_id.model_str != NULL);
|
2007-04-21 22:08:31 +08:00
|
|
|
|
|
|
|
/* ec is required because many other handles are relative to it */
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(ec);
|
2007-04-21 22:08:31 +08:00
|
|
|
if (!ec_handle) {
|
|
|
|
if (is_thinkpad)
|
|
|
|
printk(IBM_ERR
|
|
|
|
"Not yet supported ThinkPad detected!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:34 +08:00
|
|
|
/*
|
|
|
|
* Risks a regression on very old machines, but reduces potential
|
|
|
|
* false positives a damn great deal
|
|
|
|
*/
|
|
|
|
if (!is_thinkpad)
|
2007-07-19 10:45:42 +08:00
|
|
|
is_thinkpad = (thinkpad_id.vendor == PCI_VENDOR_ID_IBM);
|
2007-04-21 22:08:34 +08:00
|
|
|
|
|
|
|
if (!is_thinkpad && !force_load)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2007-04-21 22:08:31 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
/* Module init, exit, parameters */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
static struct ibm_init_struct ibms_init[] __initdata = {
|
|
|
|
{
|
|
|
|
.init = thinkpad_acpi_driver_init,
|
|
|
|
.data = &thinkpad_acpi_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = hotkey_init,
|
|
|
|
.data = &hotkey_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = bluetooth_init,
|
|
|
|
.data = &bluetooth_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = wan_init,
|
|
|
|
.data = &wan_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = video_init,
|
|
|
|
.data = &video_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = light_init,
|
|
|
|
.data = &light_driver_data,
|
|
|
|
},
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DOCK
|
|
|
|
{
|
|
|
|
.init = dock_init,
|
|
|
|
.data = &dock_driver_data[0],
|
|
|
|
},
|
|
|
|
{
|
2007-04-28 09:00:15 +08:00
|
|
|
.init = dock_init2,
|
2007-04-21 22:08:33 +08:00
|
|
|
.data = &dock_driver_data[1],
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_BAY
|
|
|
|
{
|
|
|
|
.init = bay_init,
|
|
|
|
.data = &bay_driver_data,
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
.init = cmos_init,
|
|
|
|
.data = &cmos_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = led_init,
|
|
|
|
.data = &led_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = beep_init,
|
|
|
|
.data = &beep_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = thermal_init,
|
|
|
|
.data = &thermal_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.data = &ecdump_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = brightness_init,
|
|
|
|
.data = &brightness_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.data = &volume_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.init = fan_init,
|
|
|
|
.data = &fan_driver_data,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2007-02-07 05:13:44 +08:00
|
|
|
static int __init set_ibm_param(const char *val, struct kernel_param *kp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
2007-04-21 22:08:33 +08:00
|
|
|
struct ibm_struct *ibm;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-11-18 19:18:29 +08:00
|
|
|
if (!kp || !kp->name || !val)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
|
|
|
|
ibm = ibms_init[i].data;
|
2007-11-18 19:18:29 +08:00
|
|
|
WARN_ON(ibm == NULL);
|
|
|
|
|
|
|
|
if (!ibm || !ibm->name)
|
|
|
|
continue;
|
2007-04-21 22:08:33 +08:00
|
|
|
|
|
|
|
if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
|
|
|
|
if (strlen(val) > sizeof(ibms_init[i].param) - 2)
|
2005-08-17 12:00:00 +08:00
|
|
|
return -ENOSPC;
|
2007-04-21 22:08:33 +08:00
|
|
|
strcpy(ibms_init[i].param, val);
|
|
|
|
strcat(ibms_init[i].param, ",");
|
2005-08-17 12:00:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2007-04-21 22:08:33 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
static int experimental;
|
|
|
|
module_param(experimental, int, 0);
|
|
|
|
|
2007-04-21 22:08:30 +08:00
|
|
|
static u32 dbg_level;
|
|
|
|
module_param_named(debug, dbg_level, uint, 0);
|
|
|
|
|
2007-04-21 22:08:34 +08:00
|
|
|
static int force_load;
|
2007-07-19 10:45:41 +08:00
|
|
|
module_param(force_load, bool, 0);
|
2007-04-21 22:08:34 +08:00
|
|
|
|
2007-04-28 09:00:09 +08:00
|
|
|
static int fan_control_allowed;
|
2007-07-19 10:45:41 +08:00
|
|
|
module_param_named(fan_control, fan_control_allowed, bool, 0);
|
2007-04-28 09:00:09 +08:00
|
|
|
|
2007-07-19 10:45:43 +08:00
|
|
|
static int brightness_mode;
|
|
|
|
module_param_named(brightness_mode, brightness_mode, int, 0);
|
|
|
|
|
2007-10-31 04:02:07 +08:00
|
|
|
static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
|
|
|
|
module_param(brightness_enable, uint, 0);
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
static unsigned int hotkey_report_mode;
|
|
|
|
module_param(hotkey_report_mode, uint, 0);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#define IBM_PARAM(feature) \
|
|
|
|
module_param_call(feature, set_ibm_param, NULL, NULL, 0)
|
|
|
|
|
2005-08-17 12:00:00 +08:00
|
|
|
IBM_PARAM(hotkey);
|
|
|
|
IBM_PARAM(bluetooth);
|
|
|
|
IBM_PARAM(video);
|
|
|
|
IBM_PARAM(light);
|
2007-03-29 12:58:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_DOCK
|
2005-08-17 12:00:00 +08:00
|
|
|
IBM_PARAM(dock);
|
2006-02-24 09:56:06 +08:00
|
|
|
#endif
|
2007-03-29 12:58:41 +08:00
|
|
|
#ifdef CONFIG_THINKPAD_ACPI_BAY
|
2005-08-17 12:00:00 +08:00
|
|
|
IBM_PARAM(bay);
|
2007-03-29 12:58:41 +08:00
|
|
|
#endif /* CONFIG_THINKPAD_ACPI_BAY */
|
2005-08-17 12:00:00 +08:00
|
|
|
IBM_PARAM(cmos);
|
|
|
|
IBM_PARAM(led);
|
|
|
|
IBM_PARAM(beep);
|
|
|
|
IBM_PARAM(ecdump);
|
|
|
|
IBM_PARAM(brightness);
|
|
|
|
IBM_PARAM(volume);
|
|
|
|
IBM_PARAM(fan);
|
|
|
|
|
2007-04-21 22:08:27 +08:00
|
|
|
static int __init thinkpad_acpi_module_init(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret, i;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
tpacpi_lifecycle = TPACPI_LIFE_INIT;
|
|
|
|
|
ACPI: thinkpad-acpi: revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED option
Revert new 2.6.23 CONFIG_THINKPAD_ACPI_INPUT_ENABLED Kconfig option because
it would create a legacy we don't want to support.
CONFIG_THINKPAD_ACPI_INPUT_ENABLED was added to try to fix an issue that is
now moot with the addition of the netlink ACPI event report interface to
the ACPI core.
Now that ACPI core can send events over netlink, we can use a different
strategy to keep backwards compatibility with older userspace, without the
need for the CONFIG_THINKPAD_ACPI_INPUT_ENABLED games. And it arrived
before CONFIG_THINKPAD_ACPI_INPUT_ENABLED made it to a stable mainline
kernel, even, which is Good.
This patch is in sync with some changes to thinkpad-acpi backports, that
will keep things sane for userspace across different combinations of kernel
versions, thinkpad-acpi backports (or the lack thereof), and userspace
capabilities:
Unless a module parameter is used, thinkpad-acpi will now behave in such a
way that it will work well (by default) with userspace that still uses only
the old ACPI procfs event interface and doesn't care for thinkpad-acpi
input devices.
It will also always work well with userspace that has been updated to use
both the thinkpad-acpi input devices, and ACPI core netlink event
interface, regardless of any module parameter.
The module parameter was added to allow thinkpad-acpi to work with
userspace that has been partially updated to use thinkpad-acpi input
devices, but not the new ACPI core netlink event interface. To use this
mode of hot key reporting, one has to specify the hotkey_report_mode=2
module parameter.
The thinkpad-acpi driver exports the value of hotkey_report_mode through
sysfs, as well. thinkpad-acpi backports to older kernels, that do not
support the new ACPI core netlink interface, have code to allow userspace
to switch hotkey_report_mode at runtime through sysfs. This capability
will not be provided in mainline thinkpad-acpi as it is not needed there.
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Michael S. Tsirkin <mst@dev.mellanox.co.il>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
2007-09-04 22:13:15 +08:00
|
|
|
/* Parameter checking */
|
|
|
|
if (hotkey_report_mode > 2)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/* Driver-level probe */
|
2007-07-19 10:45:42 +08:00
|
|
|
|
|
|
|
get_thinkpad_model_data(&thinkpad_id);
|
2007-04-21 22:08:31 +08:00
|
|
|
ret = probe_for_thinkpad();
|
2007-07-19 10:45:42 +08:00
|
|
|
if (ret) {
|
|
|
|
thinkpad_acpi_module_exit();
|
2007-04-21 22:08:31 +08:00
|
|
|
return ret;
|
2007-07-19 10:45:42 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
/* Driver initialization */
|
2007-07-19 10:45:42 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
IBM_ACPIHANDLE_INIT(ecrd);
|
|
|
|
IBM_ACPIHANDLE_INIT(ecwr);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-04-21 22:08:37 +08:00
|
|
|
proc_dir = proc_mkdir(IBM_PROC_DIR, acpi_root_dir);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!proc_dir) {
|
2007-04-21 22:08:37 +08:00
|
|
|
printk(IBM_ERR "unable to create proc dir " IBM_PROC_DIR);
|
2007-04-21 22:08:27 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
proc_dir->owner = THIS_MODULE;
|
2005-08-17 12:00:00 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
ret = platform_driver_register(&tpacpi_pdriver);
|
|
|
|
if (ret) {
|
2007-09-25 17:38:03 +08:00
|
|
|
printk(IBM_ERR "unable to register main platform driver\n");
|
2007-04-24 22:48:12 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-07-28 04:04:40 +08:00
|
|
|
tp_features.platform_drv_registered = 1;
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
ret = platform_driver_register(&tpacpi_hwmon_pdriver);
|
|
|
|
if (ret) {
|
|
|
|
printk(IBM_ERR "unable to register hwmon platform driver\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
tp_features.sensors_pdrv_registered = 1;
|
|
|
|
|
2007-04-24 22:48:13 +08:00
|
|
|
ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
|
2007-09-23 22:39:07 +08:00
|
|
|
if (!ret) {
|
|
|
|
tp_features.platform_drv_attrs_registered = 1;
|
|
|
|
ret = tpacpi_create_driver_attributes(&tpacpi_hwmon_pdriver.driver);
|
|
|
|
}
|
2007-04-24 22:48:13 +08:00
|
|
|
if (ret) {
|
|
|
|
printk(IBM_ERR "unable to create sysfs driver attributes\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-23 22:39:07 +08:00
|
|
|
tp_features.sensors_pdrv_attrs_registered = 1;
|
2007-04-24 22:48:13 +08:00
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
|
|
|
|
/* Device initialization */
|
|
|
|
tpacpi_pdev = platform_device_register_simple(IBM_DRVR_NAME, -1,
|
|
|
|
NULL, 0);
|
|
|
|
if (IS_ERR(tpacpi_pdev)) {
|
|
|
|
ret = PTR_ERR(tpacpi_pdev);
|
|
|
|
tpacpi_pdev = NULL;
|
|
|
|
printk(IBM_ERR "unable to register platform device\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-25 17:38:03 +08:00
|
|
|
tpacpi_sensors_pdev = platform_device_register_simple(
|
|
|
|
IBM_HWMON_DRVR_NAME,
|
|
|
|
-1, NULL, 0);
|
|
|
|
if (IS_ERR(tpacpi_sensors_pdev)) {
|
|
|
|
ret = PTR_ERR(tpacpi_sensors_pdev);
|
|
|
|
tpacpi_sensors_pdev = NULL;
|
|
|
|
printk(IBM_ERR "unable to register hwmon platform device\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = device_create_file(&tpacpi_sensors_pdev->dev,
|
|
|
|
&dev_attr_thinkpad_acpi_pdev_name);
|
|
|
|
if (ret) {
|
|
|
|
printk(IBM_ERR
|
|
|
|
"unable to create sysfs hwmon device attributes\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
tp_features.sensors_pdev_attrs_registered = 1;
|
|
|
|
tpacpi_hwmon = hwmon_device_register(&tpacpi_sensors_pdev->dev);
|
2007-04-24 22:48:12 +08:00
|
|
|
if (IS_ERR(tpacpi_hwmon)) {
|
|
|
|
ret = PTR_ERR(tpacpi_hwmon);
|
|
|
|
tpacpi_hwmon = NULL;
|
|
|
|
printk(IBM_ERR "unable to register hwmon device\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
}
|
2007-09-23 22:39:01 +08:00
|
|
|
mutex_init(&tpacpi_inputdev_send_mutex);
|
2007-07-19 10:45:34 +08:00
|
|
|
tpacpi_inputdev = input_allocate_device();
|
|
|
|
if (!tpacpi_inputdev) {
|
|
|
|
printk(IBM_ERR "unable to allocate input device\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return -ENOMEM;
|
|
|
|
} else {
|
|
|
|
/* Prepare input device, but don't register */
|
|
|
|
tpacpi_inputdev->name = "ThinkPad Extra Buttons";
|
|
|
|
tpacpi_inputdev->phys = IBM_DRVR_NAME "/input0";
|
|
|
|
tpacpi_inputdev->id.bustype = BUS_HOST;
|
2007-07-19 10:45:44 +08:00
|
|
|
tpacpi_inputdev->id.vendor = (thinkpad_id.vendor) ?
|
|
|
|
thinkpad_id.vendor :
|
|
|
|
PCI_VENDOR_ID_IBM;
|
2007-07-19 10:45:34 +08:00
|
|
|
tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
|
|
|
|
tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
|
|
|
|
}
|
2007-04-21 22:08:33 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
|
|
|
|
ret = ibm_init(&ibms_init[i]);
|
|
|
|
if (ret >= 0 && *ibms_init[i].param)
|
|
|
|
ret = ibms_init[i].data->write(ibms_init[i].param);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ret < 0) {
|
2007-04-21 22:08:27 +08:00
|
|
|
thinkpad_acpi_module_exit();
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
2007-07-19 10:45:34 +08:00
|
|
|
ret = input_register_device(tpacpi_inputdev);
|
|
|
|
if (ret < 0) {
|
|
|
|
printk(IBM_ERR "unable to register input device\n");
|
|
|
|
thinkpad_acpi_module_exit();
|
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
tp_features.input_device_registered = 1;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:27 +08:00
|
|
|
static void thinkpad_acpi_module_exit(void)
|
2007-03-24 04:33:57 +08:00
|
|
|
{
|
2007-04-21 22:08:33 +08:00
|
|
|
struct ibm_struct *ibm, *itmp;
|
|
|
|
|
2007-09-23 22:39:02 +08:00
|
|
|
tpacpi_lifecycle = TPACPI_LIFE_EXITING;
|
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
list_for_each_entry_safe_reverse(ibm, itmp,
|
|
|
|
&tpacpi_all_drivers,
|
|
|
|
all_drivers) {
|
|
|
|
ibm_exit(ibm);
|
|
|
|
}
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-04-21 22:08:33 +08:00
|
|
|
dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-07-19 10:45:34 +08:00
|
|
|
if (tpacpi_inputdev) {
|
|
|
|
if (tp_features.input_device_registered)
|
|
|
|
input_unregister_device(tpacpi_inputdev);
|
|
|
|
else
|
|
|
|
input_free_device(tpacpi_inputdev);
|
|
|
|
}
|
|
|
|
|
2007-04-24 22:48:12 +08:00
|
|
|
if (tpacpi_hwmon)
|
|
|
|
hwmon_device_unregister(tpacpi_hwmon);
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
if (tp_features.sensors_pdev_attrs_registered)
|
|
|
|
device_remove_file(&tpacpi_sensors_pdev->dev,
|
|
|
|
&dev_attr_thinkpad_acpi_pdev_name);
|
|
|
|
if (tpacpi_sensors_pdev)
|
|
|
|
platform_device_unregister(tpacpi_sensors_pdev);
|
2007-04-24 22:48:12 +08:00
|
|
|
if (tpacpi_pdev)
|
|
|
|
platform_device_unregister(tpacpi_pdev);
|
|
|
|
|
2007-09-23 22:39:07 +08:00
|
|
|
if (tp_features.sensors_pdrv_attrs_registered)
|
|
|
|
tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
|
2007-07-28 04:04:40 +08:00
|
|
|
if (tp_features.platform_drv_attrs_registered)
|
|
|
|
tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
|
|
|
|
|
2007-09-25 17:38:03 +08:00
|
|
|
if (tp_features.sensors_pdrv_registered)
|
|
|
|
platform_driver_unregister(&tpacpi_hwmon_pdriver);
|
|
|
|
|
2007-07-28 04:04:40 +08:00
|
|
|
if (tp_features.platform_drv_registered)
|
|
|
|
platform_driver_unregister(&tpacpi_pdriver);
|
2007-04-24 22:48:12 +08:00
|
|
|
|
2007-03-24 04:33:57 +08:00
|
|
|
if (proc_dir)
|
2007-04-21 22:08:37 +08:00
|
|
|
remove_proc_entry(IBM_PROC_DIR, acpi_root_dir);
|
2007-03-24 04:33:57 +08:00
|
|
|
|
2007-07-19 10:45:42 +08:00
|
|
|
kfree(thinkpad_id.bios_version_str);
|
|
|
|
kfree(thinkpad_id.ec_version_str);
|
|
|
|
kfree(thinkpad_id.model_str);
|
2007-03-24 04:33:57 +08:00
|
|
|
}
|
|
|
|
|
2007-04-21 22:08:27 +08:00
|
|
|
module_init(thinkpad_acpi_module_init);
|
|
|
|
module_exit(thinkpad_acpi_module_exit);
|