Merge branches 'acerhdf', 'acpi-pci-bind', 'bjorn-pci-root', 'bugzilla-12904', 'bugzilla-13121', 'bugzilla-13396', 'bugzilla-13533', 'bugzilla-13612', 'c3_lock', 'hid-cleanups', 'misc-2.6.31', 'pdc-leak-fix', 'pnpacpi', 'power_nocheck', 'thinkpad_acpi', 'video' and 'wmi' into release
This commit is contained in:

@@ -34,6 +34,23 @@ config ACER_WMI
|
||||
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
|
||||
here.
|
||||
|
||||
config ACERHDF
|
||||
tristate "Acer Aspire One temperature and fan driver"
|
||||
depends on THERMAL && THERMAL_HWMON && ACPI
|
||||
---help---
|
||||
This is a driver for Acer Aspire One netbooks. It allows to access
|
||||
the temperature sensor and to control the fan.
|
||||
|
||||
After loading this driver the BIOS is still in control of the fan.
|
||||
To let the kernel handle the fan, do:
|
||||
echo -n enabled > /sys/class/thermal/thermal_zone0/mode
|
||||
|
||||
For more information about this driver see
|
||||
<http://piie.net/files/acerhdf_README.txt>
|
||||
|
||||
If you have an Acer Aspire One netbook, say Y or M
|
||||
here.
|
||||
|
||||
config ASUS_LAPTOP
|
||||
tristate "Asus Laptop Extras (EXPERIMENTAL)"
|
||||
depends on ACPI
|
||||
|
@@ -9,6 +9,7 @@ obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
|
||||
obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
|
||||
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
|
||||
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
||||
obj-$(CONFIG_ACERHDF) += acerhdf.o
|
||||
obj-$(CONFIG_HP_WMI) += hp-wmi.o
|
||||
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
|
||||
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
|
||||
|
602
drivers/platform/x86/acerhdf.c
Normal file
602
drivers/platform/x86/acerhdf.c
Normal file
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* acerhdf - A driver which monitors the temperature
|
||||
* of the aspire one netbook, turns on/off the fan
|
||||
* as soon as the upper/lower threshold is reached.
|
||||
*
|
||||
* (C) 2009 - Peter Feuerer peter (a) piie.net
|
||||
* http://piie.net
|
||||
* 2009 Borislav Petkov <petkovbb@gmail.com>
|
||||
*
|
||||
* Inspired by and many thanks to:
|
||||
* o acerfand - Rachel Greenham
|
||||
* o acer_ec.pl - Michael Kurz michi.kurz (at) googlemail.com
|
||||
* - Petr Tomasek tomasek (#) etf,cuni,cz
|
||||
* - Carlos Corbacho cathectic (at) gmail.com
|
||||
* o lkml - Matthew Garrett
|
||||
* - Borislav Petkov
|
||||
* - Andreas Mohr
|
||||
*
|
||||
* 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
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "acerhdf: " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <acpi/acpi_drivers.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
/*
|
||||
* The driver is started with "kernel mode off" by default. That means, the BIOS
|
||||
* is still in control of the fan. In this mode the driver allows to read the
|
||||
* temperature of the cpu and a userspace tool may take over control of the fan.
|
||||
* If the driver is switched to "kernel mode" (e.g. via module parameter) the
|
||||
* driver is in full control of the fan. If you want the module to be started in
|
||||
* kernel mode by default, define the following:
|
||||
*/
|
||||
#undef START_IN_KERNEL_MODE
|
||||
|
||||
#define DRV_VER "0.5.13"
|
||||
|
||||
/*
|
||||
* According to the Atom N270 datasheet,
|
||||
* (http://download.intel.com/design/processor/datashts/320032.pdf) the
|
||||
* CPU's optimal operating limits denoted in junction temperature as
|
||||
* measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
|
||||
* assume 89°C is critical temperature.
|
||||
*/
|
||||
#define ACERHDF_TEMP_CRIT 89
|
||||
#define ACERHDF_FAN_OFF 0
|
||||
#define ACERHDF_FAN_AUTO 1
|
||||
|
||||
/*
|
||||
* No matter what value the user puts into the fanon variable, turn on the fan
|
||||
* at 80 degree Celsius to prevent hardware damage
|
||||
*/
|
||||
#define ACERHDF_MAX_FANON 80
|
||||
|
||||
/*
|
||||
* Maximum interval between two temperature checks is 15 seconds, as the die
|
||||
* can get hot really fast under heavy load (plus we shouldn't forget about
|
||||
* possible impact of _external_ aggressive sources such as heaters, sun etc.)
|
||||
*/
|
||||
#define ACERHDF_MAX_INTERVAL 15
|
||||
|
||||
#ifdef START_IN_KERNEL_MODE
|
||||
static int kernelmode = 1;
|
||||
#else
|
||||
static int kernelmode;
|
||||
#endif
|
||||
|
||||
static unsigned int interval = 10;
|
||||
static unsigned int fanon = 63;
|
||||
static unsigned int fanoff = 58;
|
||||
static unsigned int verbose;
|
||||
static unsigned int fanstate = ACERHDF_FAN_AUTO;
|
||||
static char force_bios[16];
|
||||
static unsigned int prev_interval;
|
||||
struct thermal_zone_device *thz_dev;
|
||||
struct thermal_cooling_device *cl_dev;
|
||||
struct platform_device *acerhdf_dev;
|
||||
|
||||
module_param(kernelmode, uint, 0);
|
||||
MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off");
|
||||
module_param(interval, uint, 0600);
|
||||
MODULE_PARM_DESC(interval, "Polling interval of temperature check");
|
||||
module_param(fanon, uint, 0600);
|
||||
MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
|
||||
module_param(fanoff, uint, 0600);
|
||||
MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
|
||||
module_param(verbose, uint, 0600);
|
||||
MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
|
||||
module_param_string(force_bios, force_bios, 16, 0);
|
||||
MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
|
||||
|
||||
/* BIOS settings */
|
||||
struct bios_settings_t {
|
||||
const char *vendor;
|
||||
const char *version;
|
||||
unsigned char fanreg;
|
||||
unsigned char tempreg;
|
||||
unsigned char fancmd[2]; /* fan off and auto commands */
|
||||
};
|
||||
|
||||
/* Register addresses and values for different BIOS versions */
|
||||
static const struct bios_settings_t bios_tbl[] = {
|
||||
{"Acer", "v0.3109", 0x55, 0x58, {0x1f, 0x00} },
|
||||
{"Acer", "v0.3114", 0x55, 0x58, {0x1f, 0x00} },
|
||||
{"Acer", "v0.3301", 0x55, 0x58, {0xaf, 0x00} },
|
||||
{"Acer", "v0.3304", 0x55, 0x58, {0xaf, 0x00} },
|
||||
{"Acer", "v0.3305", 0x55, 0x58, {0xaf, 0x00} },
|
||||
{"Acer", "v0.3308", 0x55, 0x58, {0x21, 0x00} },
|
||||
{"Acer", "v0.3309", 0x55, 0x58, {0x21, 0x00} },
|
||||
{"Acer", "v0.3310", 0x55, 0x58, {0x21, 0x00} },
|
||||
{"Gateway", "v0.3103", 0x55, 0x58, {0x21, 0x00} },
|
||||
{"Packard Bell", "v0.3105", 0x55, 0x58, {0x21, 0x00} },
|
||||
{"", "", 0, 0, {0, 0} }
|
||||
};
|
||||
|
||||
static const struct bios_settings_t *bios_cfg __read_mostly;
|
||||
|
||||
|
||||
static int acerhdf_get_temp(int *temp)
|
||||
{
|
||||
u8 read_temp;
|
||||
|
||||
if (ec_read(bios_cfg->tempreg, &read_temp))
|
||||
return -EINVAL;
|
||||
|
||||
*temp = read_temp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_get_fanstate(int *state)
|
||||
{
|
||||
u8 fan;
|
||||
bool tmp;
|
||||
|
||||
if (ec_read(bios_cfg->fanreg, &fan))
|
||||
return -EINVAL;
|
||||
|
||||
tmp = (fan == bios_cfg->fancmd[ACERHDF_FAN_OFF]);
|
||||
*state = tmp ? ACERHDF_FAN_OFF : ACERHDF_FAN_AUTO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acerhdf_change_fanstate(int state)
|
||||
{
|
||||
unsigned char cmd;
|
||||
|
||||
if (verbose)
|
||||
pr_notice("fan %s\n", (state == ACERHDF_FAN_OFF) ?
|
||||
"OFF" : "ON");
|
||||
|
||||
if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
|
||||
pr_err("invalid fan state %d requested, setting to auto!\n",
|
||||
state);
|
||||
state = ACERHDF_FAN_AUTO;
|
||||
}
|
||||
|
||||
cmd = bios_cfg->fancmd[state];
|
||||
fanstate = state;
|
||||
|
||||
ec_write(bios_cfg->fanreg, cmd);
|
||||
}
|
||||
|
||||
static void acerhdf_check_param(struct thermal_zone_device *thermal)
|
||||
{
|
||||
if (fanon > ACERHDF_MAX_FANON) {
|
||||
pr_err("fanon temperature too high, set to %d\n",
|
||||
ACERHDF_MAX_FANON);
|
||||
fanon = ACERHDF_MAX_FANON;
|
||||
}
|
||||
|
||||
if (kernelmode && prev_interval != interval) {
|
||||
if (interval > ACERHDF_MAX_INTERVAL) {
|
||||
pr_err("interval too high, set to %d\n",
|
||||
ACERHDF_MAX_INTERVAL);
|
||||
interval = ACERHDF_MAX_INTERVAL;
|
||||
}
|
||||
if (verbose)
|
||||
pr_notice("interval changed to: %d\n",
|
||||
interval);
|
||||
thermal->polling_delay = interval*1000;
|
||||
prev_interval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the thermal zone callback which does the delayed polling of the fan
|
||||
* state. We do check /sysfs-originating settings here in acerhdf_check_param()
|
||||
* as late as the polling interval is since we can't do that in the respective
|
||||
* accessors of the module parameters.
|
||||
*/
|
||||
static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *t)
|
||||
{
|
||||
int temp, err = 0;
|
||||
|
||||
acerhdf_check_param(thermal);
|
||||
|
||||
err = acerhdf_get_temp(&temp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (verbose)
|
||||
pr_notice("temp %d\n", temp);
|
||||
|
||||
*t = temp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_bind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
/* if the cooling device is the one from acerhdf bind it */
|
||||
if (cdev != cl_dev)
|
||||
return 0;
|
||||
|
||||
if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) {
|
||||
pr_err("error binding cooling dev\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_unbind(struct thermal_zone_device *thermal,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{
|
||||
if (cdev != cl_dev)
|
||||
return 0;
|
||||
|
||||
if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
|
||||
pr_err("error unbinding cooling dev\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void acerhdf_revert_to_bios_mode(void)
|
||||
{
|
||||
acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
|
||||
kernelmode = 0;
|
||||
if (thz_dev)
|
||||
thz_dev->polling_delay = 0;
|
||||
pr_notice("kernel mode fan control OFF\n");
|
||||
}
|
||||
static inline void acerhdf_enable_kernelmode(void)
|
||||
{
|
||||
kernelmode = 1;
|
||||
|
||||
thz_dev->polling_delay = interval*1000;
|
||||
thermal_zone_device_update(thz_dev);
|
||||
pr_notice("kernel mode fan control ON\n");
|
||||
}
|
||||
|
||||
static int acerhdf_get_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode *mode)
|
||||
{
|
||||
if (verbose)
|
||||
pr_notice("kernel mode fan control %d\n", kernelmode);
|
||||
|
||||
*mode = (kernelmode) ? THERMAL_DEVICE_ENABLED
|
||||
: THERMAL_DEVICE_DISABLED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* set operation mode;
|
||||
* enabled: the thermal layer of the kernel takes care about
|
||||
* the temperature and the fan.
|
||||
* disabled: the BIOS takes control of the fan.
|
||||
*/
|
||||
static int acerhdf_set_mode(struct thermal_zone_device *thermal,
|
||||
enum thermal_device_mode mode)
|
||||
{
|
||||
if (mode == THERMAL_DEVICE_DISABLED && kernelmode)
|
||||
acerhdf_revert_to_bios_mode();
|
||||
else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode)
|
||||
acerhdf_enable_kernelmode();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip,
|
||||
enum thermal_trip_type *type)
|
||||
{
|
||||
if (trip == 0)
|
||||
*type = THERMAL_TRIP_ACTIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip,
|
||||
unsigned long *temp)
|
||||
{
|
||||
if (trip == 0)
|
||||
*temp = fanon;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
|
||||
unsigned long *temperature)
|
||||
{
|
||||
*temperature = ACERHDF_TEMP_CRIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* bind callback functions to thermalzone */
|
||||
struct thermal_zone_device_ops acerhdf_dev_ops = {
|
||||
.bind = acerhdf_bind,
|
||||
.unbind = acerhdf_unbind,
|
||||
.get_temp = acerhdf_get_ec_temp,
|
||||
.get_mode = acerhdf_get_mode,
|
||||
.set_mode = acerhdf_set_mode,
|
||||
.get_trip_type = acerhdf_get_trip_type,
|
||||
.get_trip_temp = acerhdf_get_trip_temp,
|
||||
.get_crit_temp = acerhdf_get_crit_temp,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* cooling device callback functions
|
||||
* get maximal fan cooling state
|
||||
*/
|
||||
static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
*state = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
int err = 0, tmp;
|
||||
|
||||
err = acerhdf_get_fanstate(&tmp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* change current fan state - is overwritten when running in kernel mode */
|
||||
static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long state)
|
||||
{
|
||||
int cur_temp, cur_state, err = 0;
|
||||
|
||||
if (!kernelmode)
|
||||
return 0;
|
||||
|
||||
err = acerhdf_get_temp(&cur_temp);
|
||||
if (err) {
|
||||
pr_err("error reading temperature, hand off control to BIOS\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
err = acerhdf_get_fanstate(&cur_state);
|
||||
if (err) {
|
||||
pr_err("error reading fan state, hand off control to BIOS\n");
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (state == 0) {
|
||||
/* turn fan off only if below fanoff temperature */
|
||||
if ((cur_state == ACERHDF_FAN_AUTO) &&
|
||||
(cur_temp < fanoff))
|
||||
acerhdf_change_fanstate(ACERHDF_FAN_OFF);
|
||||
} else {
|
||||
if (cur_state == ACERHDF_FAN_OFF)
|
||||
acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
acerhdf_revert_to_bios_mode();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* bind fan callbacks to fan device */
|
||||
struct thermal_cooling_device_ops acerhdf_cooling_ops = {
|
||||
.get_max_state = acerhdf_get_max_state,
|
||||
.get_cur_state = acerhdf_get_cur_state,
|
||||
.set_cur_state = acerhdf_set_cur_state,
|
||||
};
|
||||
|
||||
/* suspend / resume functionality */
|
||||
static int acerhdf_suspend(struct platform_device *dev, pm_message_t state)
|
||||
{
|
||||
if (kernelmode)
|
||||
acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
|
||||
|
||||
if (verbose)
|
||||
pr_notice("going suspend\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_resume(struct platform_device *device)
|
||||
{
|
||||
if (verbose)
|
||||
pr_notice("resuming\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit acerhdf_probe(struct platform_device *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_remove(struct platform_device *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver acerhdf_drv = {
|
||||
.driver = {
|
||||
.name = "acerhdf",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = acerhdf_probe,
|
||||
.remove = acerhdf_remove,
|
||||
.suspend = acerhdf_suspend,
|
||||
.resume = acerhdf_resume,
|
||||
};
|
||||
|
||||
|
||||
/* check hardware */
|
||||
static int acerhdf_check_hardware(void)
|
||||
{
|
||||
char const *vendor, *version, *product;
|
||||
int i;
|
||||
|
||||
/* get BIOS data */
|
||||
vendor = dmi_get_system_info(DMI_SYS_VENDOR);
|
||||
version = dmi_get_system_info(DMI_BIOS_VERSION);
|
||||
product = dmi_get_system_info(DMI_PRODUCT_NAME);
|
||||
|
||||
pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER);
|
||||
|
||||
if (!force_bios[0]) {
|
||||
if (strncmp(product, "AO", 2)) {
|
||||
pr_err("no Aspire One hardware found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
pr_info("forcing BIOS version: %s\n", version);
|
||||
version = force_bios;
|
||||
kernelmode = 0;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
pr_info("BIOS info: %s %s, product: %s\n",
|
||||
vendor, version, product);
|
||||
|
||||
/* search BIOS version and vendor in BIOS settings table */
|
||||
for (i = 0; bios_tbl[i].version[0]; i++) {
|
||||
if (!strcmp(bios_tbl[i].vendor, vendor) &&
|
||||
!strcmp(bios_tbl[i].version, version)) {
|
||||
bios_cfg = &bios_tbl[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bios_cfg) {
|
||||
pr_err("unknown (unsupported) BIOS version %s/%s, "
|
||||
"please report, aborting!\n", vendor, version);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* if started with kernel mode off, prevent the kernel from switching
|
||||
* off the fan
|
||||
*/
|
||||
if (!kernelmode) {
|
||||
pr_notice("Fan control off, to enable do:\n");
|
||||
pr_notice("echo -n \"enabled\" > "
|
||||
"/sys/class/thermal/thermal_zone0/mode\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acerhdf_register_platform(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = platform_driver_register(&acerhdf_drv);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
acerhdf_dev = platform_device_alloc("acerhdf", -1);
|
||||
platform_device_add(acerhdf_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acerhdf_unregister_platform(void)
|
||||
{
|
||||
if (!acerhdf_dev)
|
||||
return;
|
||||
|
||||
platform_device_del(acerhdf_dev);
|
||||
platform_driver_unregister(&acerhdf_drv);
|
||||
}
|
||||
|
||||
static int acerhdf_register_thermal(void)
|
||||
{
|
||||
cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
|
||||
&acerhdf_cooling_ops);
|
||||
|
||||
if (IS_ERR(cl_dev))
|
||||
return -EINVAL;
|
||||
|
||||
thz_dev = thermal_zone_device_register("acerhdf", 1, NULL,
|
||||
&acerhdf_dev_ops, 0, 0, 0,
|
||||
(kernelmode) ? interval*1000 : 0);
|
||||
if (IS_ERR(thz_dev))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void acerhdf_unregister_thermal(void)
|
||||
{
|
||||
if (cl_dev) {
|
||||
thermal_cooling_device_unregister(cl_dev);
|
||||
cl_dev = NULL;
|
||||
}
|
||||
|
||||
if (thz_dev) {
|
||||
thermal_zone_device_unregister(thz_dev);
|
||||
thz_dev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int __init acerhdf_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = acerhdf_check_hardware();
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
err = acerhdf_register_platform();
|
||||
if (err)
|
||||
goto err_unreg;
|
||||
|
||||
err = acerhdf_register_thermal();
|
||||
if (err)
|
||||
goto err_unreg;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unreg:
|
||||
acerhdf_unregister_thermal();
|
||||
acerhdf_unregister_platform();
|
||||
|
||||
out_err:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void __exit acerhdf_exit(void)
|
||||
{
|
||||
acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
|
||||
acerhdf_unregister_thermal();
|
||||
acerhdf_unregister_platform();
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Peter Feuerer");
|
||||
MODULE_DESCRIPTION("Aspire One temperature and fan driver");
|
||||
MODULE_ALIAS("dmi:*:*Acer*:*:");
|
||||
MODULE_ALIAS("dmi:*:*Gateway*:*:");
|
||||
MODULE_ALIAS("dmi:*:*Packard Bell*:*:");
|
||||
|
||||
module_init(acerhdf_init);
|
||||
module_exit(acerhdf_exit);
|
@@ -46,10 +46,53 @@ struct key_entry {
|
||||
u16 keycode;
|
||||
};
|
||||
|
||||
enum { KE_KEY, KE_SW, KE_END };
|
||||
enum { KE_KEY, KE_SW, KE_IGNORE, KE_END };
|
||||
|
||||
/*
|
||||
* Certain keys are flagged as KE_IGNORE. All of these are either
|
||||
* notifications (rather than requests for change) or are also sent
|
||||
* via the keyboard controller so should not be sent again.
|
||||
*/
|
||||
|
||||
static struct key_entry dell_wmi_keymap[] = {
|
||||
{KE_KEY, 0xe045, KEY_PROG1},
|
||||
{KE_KEY, 0xe009, KEY_EJECTCD},
|
||||
|
||||
/* These also contain the brightness level at offset 6 */
|
||||
{KE_KEY, 0xe006, KEY_BRIGHTNESSUP},
|
||||
{KE_KEY, 0xe005, KEY_BRIGHTNESSDOWN},
|
||||
|
||||
/* Battery health status button */
|
||||
{KE_KEY, 0xe007, KEY_BATTERY},
|
||||
|
||||
/* This is actually for all radios. Although physically a
|
||||
* switch, the notification does not provide an indication of
|
||||
* state and so it should be reported as a key */
|
||||
{KE_KEY, 0xe008, KEY_WLAN},
|
||||
|
||||
/* The next device is at offset 6, the active devices are at
|
||||
offset 8 and the attached devices at offset 10 */
|
||||
{KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE},
|
||||
|
||||
{KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE},
|
||||
|
||||
/* BIOS error detected */
|
||||
{KE_IGNORE, 0xe00d, KEY_RESERVED},
|
||||
|
||||
/* Wifi Catcher */
|
||||
{KE_KEY, 0xe011, KEY_PROG2},
|
||||
|
||||
/* Ambient light sensor toggle */
|
||||
{KE_IGNORE, 0xe013, KEY_RESERVED},
|
||||
|
||||
{KE_IGNORE, 0xe020, KEY_MUTE},
|
||||
{KE_IGNORE, 0xe02e, KEY_VOLUMEDOWN},
|
||||
{KE_IGNORE, 0xe030, KEY_VOLUMEUP},
|
||||
{KE_IGNORE, 0xe033, KEY_KBDILLUMUP},
|
||||
{KE_IGNORE, 0xe034, KEY_KBDILLUMDOWN},
|
||||
{KE_IGNORE, 0xe03a, KEY_CAPSLOCK},
|
||||
{KE_IGNORE, 0xe045, KEY_NUMLOCK},
|
||||
{KE_IGNORE, 0xe046, KEY_SCROLLLOCK},
|
||||
{KE_END, 0}
|
||||
};
|
||||
|
||||
@@ -122,15 +165,20 @@ static void dell_wmi_notify(u32 value, void *context)
|
||||
|
||||
if (obj && obj->type == ACPI_TYPE_BUFFER) {
|
||||
int *buffer = (int *)obj->buffer.pointer;
|
||||
key = dell_wmi_get_entry_by_scancode(buffer[1]);
|
||||
/*
|
||||
* The upper bytes of the event may contain
|
||||
* additional information, so mask them off for the
|
||||
* scancode lookup
|
||||
*/
|
||||
key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF);
|
||||
if (key) {
|
||||
input_report_key(dell_wmi_input_dev, key->keycode, 1);
|
||||
input_sync(dell_wmi_input_dev);
|
||||
input_report_key(dell_wmi_input_dev, key->keycode, 0);
|
||||
input_sync(dell_wmi_input_dev);
|
||||
} else
|
||||
} else if (buffer[1] & 0xFFFF)
|
||||
printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n",
|
||||
buffer[1]);
|
||||
buffer[1] & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
|
||||
#define HPWMI_DISPLAY_QUERY 0x1
|
||||
#define HPWMI_HDDTEMP_QUERY 0x2
|
||||
#define HPWMI_ALS_QUERY 0x3
|
||||
#define HPWMI_DOCK_QUERY 0x4
|
||||
#define HPWMI_HARDWARE_QUERY 0x4
|
||||
#define HPWMI_WIRELESS_QUERY 0x5
|
||||
#define HPWMI_HOTKEY_QUERY 0xc
|
||||
|
||||
@@ -75,10 +75,9 @@ struct key_entry {
|
||||
u16 keycode;
|
||||
};
|
||||
|
||||
enum { KE_KEY, KE_SW, KE_END };
|
||||
enum { KE_KEY, KE_END };
|
||||
|
||||
static struct key_entry hp_wmi_keymap[] = {
|
||||
{KE_SW, 0x01, SW_DOCK},
|
||||
{KE_KEY, 0x02, KEY_BRIGHTNESSUP},
|
||||
{KE_KEY, 0x03, KEY_BRIGHTNESSDOWN},
|
||||
{KE_KEY, 0x20e6, KEY_PROG1},
|
||||
@@ -151,7 +150,22 @@ static int hp_wmi_als_state(void)
|
||||
|
||||
static int hp_wmi_dock_state(void)
|
||||
{
|
||||
return hp_wmi_perform_query(HPWMI_DOCK_QUERY, 0, 0);
|
||||
int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret & 0x1;
|
||||
}
|
||||
|
||||
static int hp_wmi_tablet_state(void)
|
||||
{
|
||||
int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return (ret & 0x4) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_set_block(void *data, bool blocked)
|
||||
@@ -232,6 +246,15 @@ static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
|
||||
return sprintf(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int value = hp_wmi_tablet_state();
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
return sprintf(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static ssize_t set_als(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
@@ -244,6 +267,7 @@ static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
|
||||
static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
|
||||
static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
|
||||
static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
|
||||
static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);
|
||||
|
||||
static struct key_entry *hp_wmi_get_entry_by_scancode(int code)
|
||||
{
|
||||
@@ -326,13 +350,13 @@ static void hp_wmi_notify(u32 value, void *context)
|
||||
key->keycode, 0);
|
||||
input_sync(hp_wmi_input_dev);
|
||||
break;
|
||||
case KE_SW:
|
||||
input_report_switch(hp_wmi_input_dev,
|
||||
key->keycode,
|
||||
hp_wmi_dock_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
break;
|
||||
}
|
||||
} else if (eventcode == 0x1) {
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK,
|
||||
hp_wmi_dock_state());
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
|
||||
hp_wmi_tablet_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
} else if (eventcode == 0x5) {
|
||||
if (wifi_rfkill)
|
||||
rfkill_set_sw_state(wifi_rfkill,
|
||||
@@ -369,18 +393,19 @@ static int __init hp_wmi_input_setup(void)
|
||||
set_bit(EV_KEY, hp_wmi_input_dev->evbit);
|
||||
set_bit(key->keycode, hp_wmi_input_dev->keybit);
|
||||
break;
|
||||
case KE_SW:
|
||||
set_bit(EV_SW, hp_wmi_input_dev->evbit);
|
||||
set_bit(key->keycode, hp_wmi_input_dev->swbit);
|
||||
|
||||
/* Set initial dock state */
|
||||
input_report_switch(hp_wmi_input_dev, key->keycode,
|
||||
hp_wmi_dock_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
set_bit(EV_SW, hp_wmi_input_dev->evbit);
|
||||
set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
|
||||
set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
|
||||
|
||||
/* Set initial hardware state */
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state());
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
|
||||
hp_wmi_tablet_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
|
||||
err = input_register_device(hp_wmi_input_dev);
|
||||
|
||||
if (err) {
|
||||
@@ -397,6 +422,7 @@ static void cleanup_sysfs(struct platform_device *device)
|
||||
device_remove_file(&device->dev, &dev_attr_hddtemp);
|
||||
device_remove_file(&device->dev, &dev_attr_als);
|
||||
device_remove_file(&device->dev, &dev_attr_dock);
|
||||
device_remove_file(&device->dev, &dev_attr_tablet);
|
||||
}
|
||||
|
||||
static int __init hp_wmi_bios_setup(struct platform_device *device)
|
||||
@@ -414,6 +440,9 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
|
||||
if (err)
|
||||
goto add_sysfs_error;
|
||||
err = device_create_file(&device->dev, &dev_attr_dock);
|
||||
if (err)
|
||||
goto add_sysfs_error;
|
||||
err = device_create_file(&device->dev, &dev_attr_tablet);
|
||||
if (err)
|
||||
goto add_sysfs_error;
|
||||
|
||||
@@ -485,23 +514,17 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device)
|
||||
|
||||
static int hp_wmi_resume_handler(struct platform_device *device)
|
||||
{
|
||||
struct key_entry *key;
|
||||
|
||||
/*
|
||||
* Docking state may have changed while suspended, so trigger
|
||||
* an input event for the current state. As this is a switch,
|
||||
* Hardware state may have changed while suspended, so trigger
|
||||
* input events for the current state. As this is a switch,
|
||||
* the input layer will only actually pass it on if the state
|
||||
* changed.
|
||||
*/
|
||||
for (key = hp_wmi_keymap; key->type != KE_END; key++) {
|
||||
switch (key->type) {
|
||||
case KE_SW:
|
||||
input_report_switch(hp_wmi_input_dev, key->keycode,
|
||||
hp_wmi_dock_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state());
|
||||
input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
|
||||
hp_wmi_tablet_state());
|
||||
input_sync(hp_wmi_input_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
|
||||
#define TPACPI_VERSION "0.23"
|
||||
#define TPACPI_SYSFS_VERSION 0x020300
|
||||
#define TPACPI_SYSFS_VERSION 0x020400
|
||||
|
||||
/*
|
||||
* Changelog:
|
||||
@@ -257,6 +257,8 @@ static struct {
|
||||
u32 wan:1;
|
||||
u32 uwb:1;
|
||||
u32 fan_ctrl_status_undef:1;
|
||||
u32 second_fan:1;
|
||||
u32 beep_needs_two_args:1;
|
||||
u32 input_device_registered:1;
|
||||
u32 platform_drv_registered:1;
|
||||
u32 platform_drv_attrs_registered:1;
|
||||
@@ -277,8 +279,10 @@ struct thinkpad_id_data {
|
||||
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 bios_model; /* 1Y = 0x5931, 0 = unknown */
|
||||
u16 ec_model;
|
||||
u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */
|
||||
u16 ec_release;
|
||||
|
||||
char *model_str; /* ThinkPad T43 */
|
||||
char *nummodel_str; /* 9384A9C for a 9384-A9C model */
|
||||
@@ -355,6 +359,73 @@ static void tpacpi_log_usertask(const char * const what)
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Quirk handling helpers
|
||||
*
|
||||
* ThinkPad IDs and versions seen in the field so far
|
||||
* are two-characters from the set [0-9A-Z], i.e. base 36.
|
||||
*
|
||||
* We use values well outside that range as specials.
|
||||
*/
|
||||
|
||||
#define TPACPI_MATCH_ANY 0xffffU
|
||||
#define TPACPI_MATCH_UNKNOWN 0U
|
||||
|
||||
/* TPID('1', 'Y') == 0x5931 */
|
||||
#define TPID(__c1, __c2) (((__c2) << 8) | (__c1))
|
||||
|
||||
#define TPACPI_Q_IBM(__id1, __id2, __quirk) \
|
||||
{ .vendor = PCI_VENDOR_ID_IBM, \
|
||||
.bios = TPID(__id1, __id2), \
|
||||
.ec = TPACPI_MATCH_ANY, \
|
||||
.quirks = (__quirk) }
|
||||
|
||||
#define TPACPI_Q_LNV(__id1, __id2, __quirk) \
|
||||
{ .vendor = PCI_VENDOR_ID_LENOVO, \
|
||||
.bios = TPID(__id1, __id2), \
|
||||
.ec = TPACPI_MATCH_ANY, \
|
||||
.quirks = (__quirk) }
|
||||
|
||||
struct tpacpi_quirk {
|
||||
unsigned int vendor;
|
||||
u16 bios;
|
||||
u16 ec;
|
||||
unsigned long quirks;
|
||||
};
|
||||
|
||||
/**
|
||||
* tpacpi_check_quirks() - search BIOS/EC version on a list
|
||||
* @qlist: array of &struct tpacpi_quirk
|
||||
* @qlist_size: number of elements in @qlist
|
||||
*
|
||||
* Iterates over a quirks list until one is found that matches the
|
||||
* ThinkPad's vendor, BIOS and EC model.
|
||||
*
|
||||
* Returns 0 if nothing matches, otherwise returns the quirks field of
|
||||
* the matching &struct tpacpi_quirk entry.
|
||||
*
|
||||
* The match criteria is: vendor, ec and bios much match.
|
||||
*/
|
||||
static unsigned long __init tpacpi_check_quirks(
|
||||
const struct tpacpi_quirk *qlist,
|
||||
unsigned int qlist_size)
|
||||
{
|
||||
while (qlist_size) {
|
||||
if ((qlist->vendor == thinkpad_id.vendor ||
|
||||
qlist->vendor == TPACPI_MATCH_ANY) &&
|
||||
(qlist->bios == thinkpad_id.bios_model ||
|
||||
qlist->bios == TPACPI_MATCH_ANY) &&
|
||||
(qlist->ec == thinkpad_id.ec_model ||
|
||||
qlist->ec == TPACPI_MATCH_ANY))
|
||||
return qlist->quirks;
|
||||
|
||||
qlist_size--;
|
||||
qlist++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
****************************************************************************
|
||||
*
|
||||
@@ -2880,7 +2951,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
|
||||
/* update bright_acpimode... */
|
||||
tpacpi_check_std_acpi_brightness_support();
|
||||
|
||||
if (tp_features.bright_acpimode) {
|
||||
if (tp_features.bright_acpimode && acpi_video_backlight_support()) {
|
||||
printk(TPACPI_INFO
|
||||
"This ThinkPad has standard ACPI backlight "
|
||||
"brightness control, supported by the ACPI "
|
||||
@@ -4773,7 +4844,7 @@ TPACPI_HANDLE(led, ec, "SLED", /* 570 */
|
||||
"LED", /* all others */
|
||||
); /* R30, R31 */
|
||||
|
||||
#define TPACPI_LED_NUMLEDS 8
|
||||
#define TPACPI_LED_NUMLEDS 16
|
||||
static struct tpacpi_led_classdev *tpacpi_leds;
|
||||
static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];
|
||||
static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
|
||||
@@ -4786,15 +4857,20 @@ static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
|
||||
"tpacpi::dock_batt",
|
||||
"tpacpi::unknown_led",
|
||||
"tpacpi::standby",
|
||||
"tpacpi::dock_status1",
|
||||
"tpacpi::dock_status2",
|
||||
"tpacpi::unknown_led2",
|
||||
"tpacpi::unknown_led3",
|
||||
"tpacpi::thinkvantage",
|
||||
};
|
||||
#define TPACPI_SAFE_LEDS 0x0081U
|
||||
#define TPACPI_SAFE_LEDS 0x1081U
|
||||
|
||||
static inline bool tpacpi_is_led_restricted(const unsigned int led)
|
||||
{
|
||||
#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
|
||||
return false;
|
||||
#else
|
||||
return (TPACPI_SAFE_LEDS & (1 << led)) == 0;
|
||||
return (1U & (TPACPI_SAFE_LEDS >> led)) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4956,6 +5032,10 @@ static int __init tpacpi_init_led(unsigned int led)
|
||||
|
||||
tpacpi_leds[led].led = led;
|
||||
|
||||
/* LEDs with no name don't get registered */
|
||||
if (!tpacpi_led_names[led])
|
||||
return 0;
|
||||
|
||||
tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set;
|
||||
tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set;
|
||||
if (led_supported == TPACPI_LED_570)
|
||||
@@ -4974,10 +5054,59 @@ static int __init tpacpi_init_led(unsigned int led)
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
|
||||
TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */
|
||||
TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */
|
||||
TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */
|
||||
|
||||
TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */
|
||||
TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */
|
||||
TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */
|
||||
TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */
|
||||
TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */
|
||||
TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */
|
||||
TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */
|
||||
TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */
|
||||
|
||||
TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */
|
||||
TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */
|
||||
TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */
|
||||
TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */
|
||||
TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */
|
||||
|
||||
TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */
|
||||
TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */
|
||||
TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */
|
||||
TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */
|
||||
|
||||
/* (1) - may have excess leds enabled on MSB */
|
||||
|
||||
/* Defaults (order matters, keep last, don't reorder!) */
|
||||
{ /* Lenovo */
|
||||
.vendor = PCI_VENDOR_ID_LENOVO,
|
||||
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
||||
.quirks = 0x1fffU,
|
||||
},
|
||||
{ /* IBM ThinkPads with no EC version string */
|
||||
.vendor = PCI_VENDOR_ID_IBM,
|
||||
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN,
|
||||
.quirks = 0x00ffU,
|
||||
},
|
||||
{ /* IBM ThinkPads with EC version string */
|
||||
.vendor = PCI_VENDOR_ID_IBM,
|
||||
.bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
|
||||
.quirks = 0x00bfU,
|
||||
},
|
||||
};
|
||||
|
||||
#undef TPACPI_LEDQ_IBM
|
||||
#undef TPACPI_LEDQ_LNV
|
||||
|
||||
static int __init led_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
unsigned int i;
|
||||
int rc;
|
||||
unsigned long useful_leds;
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
|
||||
|
||||
@@ -4999,6 +5128,9 @@ static int __init led_init(struct ibm_init_struct *iibm)
|
||||
vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
|
||||
str_supported(led_supported), led_supported);
|
||||
|
||||
if (led_supported == TPACPI_LED_NONE)
|
||||
return 1;
|
||||
|
||||
tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
|
||||
GFP_KERNEL);
|
||||
if (!tpacpi_leds) {
|
||||
@@ -5006,8 +5138,12 @@ static int __init led_init(struct ibm_init_struct *iibm)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
useful_leds = tpacpi_check_quirks(led_useful_qtable,
|
||||
ARRAY_SIZE(led_useful_qtable));
|
||||
|
||||
for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
|
||||
if (!tpacpi_is_led_restricted(i)) {
|
||||
if (!tpacpi_is_led_restricted(i) &&
|
||||
test_bit(i, &useful_leds)) {
|
||||
rc = tpacpi_init_led(i);
|
||||
if (rc < 0) {
|
||||
led_exit();
|
||||
@@ -5017,12 +5153,11 @@ static int __init led_init(struct ibm_init_struct *iibm)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
|
||||
if (led_supported != TPACPI_LED_NONE)
|
||||
printk(TPACPI_NOTICE
|
||||
"warning: userspace override of important "
|
||||
"firmware LEDs is enabled\n");
|
||||
printk(TPACPI_NOTICE
|
||||
"warning: userspace override of important "
|
||||
"firmware LEDs is enabled\n");
|
||||
#endif
|
||||
return (led_supported != TPACPI_LED_NONE)? 0 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define str_led_status(s) \
|
||||
@@ -5052,7 +5187,7 @@ static int led_read(char *p)
|
||||
}
|
||||
|
||||
len += sprintf(p + len, "commands:\t"
|
||||
"<led> on, <led> off, <led> blink (<led> is 0-7)\n");
|
||||
"<led> on, <led> off, <led> blink (<led> is 0-15)\n");
|
||||
|
||||
return len;
|
||||
}
|
||||
@@ -5067,7 +5202,7 @@ static int led_write(char *buf)
|
||||
return -ENODEV;
|
||||
|
||||
while ((cmd = next_cmd(&buf))) {
|
||||
if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 7)
|
||||
if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15)
|
||||
return -EINVAL;
|
||||
|
||||
if (strstr(cmd, "off")) {
|
||||
@@ -5101,8 +5236,17 @@ static struct ibm_struct led_driver_data = {
|
||||
|
||||
TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
|
||||
|
||||
#define TPACPI_BEEP_Q1 0x0001
|
||||
|
||||
static const struct tpacpi_quirk beep_quirk_table[] __initconst = {
|
||||
TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */
|
||||
TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */
|
||||
};
|
||||
|
||||
static int __init beep_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
unsigned long quirks;
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
|
||||
|
||||
TPACPI_ACPIHANDLE_INIT(beep);
|
||||
@@ -5110,6 +5254,11 @@ static int __init beep_init(struct ibm_init_struct *iibm)
|
||||
vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
|
||||
str_supported(beep_handle != NULL));
|
||||
|
||||
quirks = tpacpi_check_quirks(beep_quirk_table,
|
||||
ARRAY_SIZE(beep_quirk_table));
|
||||
|
||||
tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
|
||||
|
||||
return (beep_handle)? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -5141,8 +5290,15 @@ static int beep_write(char *buf)
|
||||
/* beep_cmd set */
|
||||
} else
|
||||
return -EINVAL;
|
||||
if (!acpi_evalf(beep_handle, NULL, NULL, "vdd", beep_cmd, 0))
|
||||
return -EIO;
|
||||
if (tp_features.beep_needs_two_args) {
|
||||
if (!acpi_evalf(beep_handle, NULL, NULL, "vdd",
|
||||
beep_cmd, 0))
|
||||
return -EIO;
|
||||
} else {
|
||||
if (!acpi_evalf(beep_handle, NULL, NULL, "vd",
|
||||
beep_cmd))
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -5569,6 +5725,10 @@ static struct ibm_struct ecdump_driver_data = {
|
||||
* Bit 3-0: backlight brightness level
|
||||
*
|
||||
* brightness_get_raw returns status data in the HBRV layout
|
||||
*
|
||||
* WARNING: The X61 has been verified to use HBRV for something else, so
|
||||
* this should be used _only_ on IBM ThinkPads, and maybe with some careful
|
||||
* testing on the very early *60 Lenovo models...
|
||||
*/
|
||||
|
||||
enum {
|
||||
@@ -5869,6 +6029,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
|
||||
brightness_mode);
|
||||
}
|
||||
|
||||
/* Safety */
|
||||
if (thinkpad_id.vendor != PCI_VENDOR_ID_IBM &&
|
||||
(brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
|
||||
brightness_mode == TPACPI_BRGHT_MODE_EC))
|
||||
return -EINVAL;
|
||||
|
||||
if (tpacpi_brightness_get_raw(&b) < 0)
|
||||
return 1;
|
||||
|
||||
@@ -6161,6 +6327,21 @@ static struct ibm_struct volume_driver_data = {
|
||||
* For firmware bugs, refer to:
|
||||
* http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
|
||||
*
|
||||
* ----
|
||||
*
|
||||
* ThinkPad EC register 0x31 bit 0 (only on select models)
|
||||
*
|
||||
* When bit 0 of EC register 0x31 is zero, the tachometer registers
|
||||
* show the speed of the main fan. When bit 0 of EC register 0x31
|
||||
* is one, the tachometer registers show the speed of the auxiliary
|
||||
* fan.
|
||||
*
|
||||
* Fan control seems to affect both fans, regardless of the state
|
||||
* of this bit.
|
||||
*
|
||||
* So far, only the firmware for the X60/X61 non-tablet versions
|
||||
* seem to support this (firmware TP-7M).
|
||||
*
|
||||
* TPACPI_FAN_WR_ACPI_FANS:
|
||||
* ThinkPad X31, X40, X41. Not available in the X60.
|
||||
*
|
||||
@@ -6187,6 +6368,8 @@ 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 */
|
||||
fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M)
|
||||
bit 0 selects which fan is active */
|
||||
|
||||
TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
|
||||
TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
|
||||
@@ -6249,30 +6432,18 @@ TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */
|
||||
* We assume 0x07 really means auto mode while this quirk is active,
|
||||
* as this is far more likely than the ThinkPad being in level 7,
|
||||
* which is only used by the firmware during thermal emergencies.
|
||||
*
|
||||
* Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52),
|
||||
* TP-70 (T43, R52), which are known to be buggy.
|
||||
*/
|
||||
|
||||
static void fan_quirk1_detect(void)
|
||||
static void fan_quirk1_setup(void)
|
||||
{
|
||||
/* In some ThinkPads, neither the EC nor the ACPI
|
||||
* DSDT initialize the HFSP register, and it ends up
|
||||
* being initially 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. */
|
||||
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(TPACPI_NOTICE
|
||||
"fan_init: initial fan status is unknown, "
|
||||
"assuming it is in auto mode\n");
|
||||
tp_features.fan_ctrl_status_undef = 1;
|
||||
;;
|
||||
}
|
||||
printk(TPACPI_NOTICE
|
||||
"fan_init: initial fan status is unknown, "
|
||||
"assuming it is in auto mode\n");
|
||||
tp_features.fan_ctrl_status_undef = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6292,6 +6463,38 @@ static void fan_quirk1_handle(u8 *fan_status)
|
||||
}
|
||||
}
|
||||
|
||||
/* Select main fan on X60/X61, NOOP on others */
|
||||
static bool fan_select_fan1(void)
|
||||
{
|
||||
if (tp_features.second_fan) {
|
||||
u8 val;
|
||||
|
||||
if (ec_read(fan_select_offset, &val) < 0)
|
||||
return false;
|
||||
val &= 0xFEU;
|
||||
if (ec_write(fan_select_offset, val) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Select secondary fan on X60/X61 */
|
||||
static bool fan_select_fan2(void)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
if (!tp_features.second_fan)
|
||||
return false;
|
||||
|
||||
if (ec_read(fan_select_offset, &val) < 0)
|
||||
return false;
|
||||
val |= 0x01U;
|
||||
if (ec_write(fan_select_offset, val) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call with fan_mutex held
|
||||
*/
|
||||
@@ -6369,6 +6572,8 @@ static int fan_get_speed(unsigned int *speed)
|
||||
switch (fan_status_access_mode) {
|
||||
case TPACPI_FAN_RD_TPEC:
|
||||
/* all except 570, 600e/x, 770e, 770x */
|
||||
if (unlikely(!fan_select_fan1()))
|
||||
return -EIO;
|
||||
if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
|
||||
!acpi_ec_read(fan_rpm_offset + 1, &hi)))
|
||||
return -EIO;
|
||||
@@ -6385,6 +6590,34 @@ static int fan_get_speed(unsigned int *speed)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fan2_get_speed(unsigned int *speed)
|
||||
{
|
||||
u8 hi, lo;
|
||||
bool rc;
|
||||
|
||||
switch (fan_status_access_mode) {
|
||||
case TPACPI_FAN_RD_TPEC:
|
||||
/* all except 570, 600e/x, 770e, 770x */
|
||||
if (unlikely(!fan_select_fan2()))
|
||||
return -EIO;
|
||||
rc = !acpi_ec_read(fan_rpm_offset, &lo) ||
|
||||
!acpi_ec_read(fan_rpm_offset + 1, &hi);
|
||||
fan_select_fan1(); /* play it safe */
|
||||
if (rc)
|
||||
return -EIO;
|
||||
|
||||
if (likely(speed))
|
||||
*speed = (hi << 8) | lo;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fan_set_level(int level)
|
||||
{
|
||||
if (!fan_control_allowed)
|
||||
@@ -6790,6 +7023,25 @@ static struct device_attribute dev_attr_fan_fan1_input =
|
||||
__ATTR(fan1_input, S_IRUGO,
|
||||
fan_fan1_input_show, NULL);
|
||||
|
||||
/* sysfs fan fan2_input ------------------------------------------------ */
|
||||
static ssize_t fan_fan2_input_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int res;
|
||||
unsigned int speed;
|
||||
|
||||
res = fan2_get_speed(&speed);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", speed);
|
||||
}
|
||||
|
||||
static struct device_attribute dev_attr_fan_fan2_input =
|
||||
__ATTR(fan2_input, S_IRUGO,
|
||||
fan_fan2_input_show, NULL);
|
||||
|
||||
/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
|
||||
static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
|
||||
char *buf)
|
||||
@@ -6823,6 +7075,7 @@ static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
|
||||
static struct attribute *fan_attributes[] = {
|
||||
&dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
|
||||
&dev_attr_fan_fan1_input.attr,
|
||||
NULL, /* for fan2_input */
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -6830,9 +7083,36 @@ static const struct attribute_group fan_attr_group = {
|
||||
.attrs = fan_attributes,
|
||||
};
|
||||
|
||||
#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */
|
||||
#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */
|
||||
|
||||
#define TPACPI_FAN_QI(__id1, __id2, __quirks) \
|
||||
{ .vendor = PCI_VENDOR_ID_IBM, \
|
||||
.bios = TPACPI_MATCH_ANY, \
|
||||
.ec = TPID(__id1, __id2), \
|
||||
.quirks = __quirks }
|
||||
|
||||
#define TPACPI_FAN_QL(__id1, __id2, __quirks) \
|
||||
{ .vendor = PCI_VENDOR_ID_LENOVO, \
|
||||
.bios = TPACPI_MATCH_ANY, \
|
||||
.ec = TPID(__id1, __id2), \
|
||||
.quirks = __quirks }
|
||||
|
||||
static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
|
||||
TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1),
|
||||
TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1),
|
||||
TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1),
|
||||
TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1),
|
||||
TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN),
|
||||
};
|
||||
|
||||
#undef TPACPI_FAN_QL
|
||||
#undef TPACPI_FAN_QI
|
||||
|
||||
static int __init fan_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
int rc;
|
||||
unsigned long quirks;
|
||||
|
||||
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
||||
"initializing fan subdriver\n");
|
||||
@@ -6843,12 +7123,16 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
||||
fan_control_commands = 0;
|
||||
fan_watchdog_maxinterval = 0;
|
||||
tp_features.fan_ctrl_status_undef = 0;
|
||||
tp_features.second_fan = 0;
|
||||
fan_control_desired_level = 7;
|
||||
|
||||
TPACPI_ACPIHANDLE_INIT(fans);
|
||||
TPACPI_ACPIHANDLE_INIT(gfan);
|
||||
TPACPI_ACPIHANDLE_INIT(sfan);
|
||||
|
||||
quirks = tpacpi_check_quirks(fan_quirk_table,
|
||||
ARRAY_SIZE(fan_quirk_table));
|
||||
|
||||
if (gfan_handle) {
|
||||
/* 570, 600e/x, 770e, 770x */
|
||||
fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
|
||||
@@ -6858,7 +7142,13 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
||||
if (likely(acpi_ec_read(fan_status_offset,
|
||||
&fan_control_initial_status))) {
|
||||
fan_status_access_mode = TPACPI_FAN_RD_TPEC;
|
||||
fan_quirk1_detect();
|
||||
if (quirks & TPACPI_FAN_Q1)
|
||||
fan_quirk1_setup();
|
||||
if (quirks & TPACPI_FAN_2FAN) {
|
||||
tp_features.second_fan = 1;
|
||||
dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
|
||||
"secondary fan support enabled\n");
|
||||
}
|
||||
} else {
|
||||
printk(TPACPI_ERR
|
||||
"ThinkPad ACPI EC access misbehaving, "
|
||||
@@ -6914,6 +7204,11 @@ static int __init fan_init(struct ibm_init_struct *iibm)
|
||||
|
||||
if (fan_status_access_mode != TPACPI_FAN_NONE ||
|
||||
fan_control_access_mode != TPACPI_FAN_WR_NONE) {
|
||||
if (tp_features.second_fan) {
|
||||
/* attach second fan tachometer */
|
||||
fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
|
||||
&dev_attr_fan_fan2_input.attr;
|
||||
}
|
||||
rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
|
||||
&fan_attr_group);
|
||||
if (rc < 0)
|
||||
@@ -7385,6 +7680,24 @@ err_out:
|
||||
|
||||
/* Probing */
|
||||
|
||||
static bool __pure __init tpacpi_is_fw_digit(const char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */
|
||||
static bool __pure __init tpacpi_is_valid_fw_id(const char* const s,
|
||||
const char t)
|
||||
{
|
||||
return s && strlen(s) >= 8 &&
|
||||
tpacpi_is_fw_digit(s[0]) &&
|
||||
tpacpi_is_fw_digit(s[1]) &&
|
||||
s[2] == t && s[3] == 'T' &&
|
||||
tpacpi_is_fw_digit(s[4]) &&
|
||||
tpacpi_is_fw_digit(s[5]) &&
|
||||
s[6] == 'W' && s[7] == 'W';
|
||||
}
|
||||
|
||||
/* returns 0 - probe ok, or < 0 - probe error.
|
||||
* Probe ok doesn't mean thinkpad found.
|
||||
* On error, kfree() cleanup on tp->* is not performed, caller must do it */
|
||||
@@ -7411,10 +7724,15 @@ static int __must_check __init get_thinkpad_model_data(
|
||||
tp->bios_version_str = kstrdup(s, GFP_KERNEL);
|
||||
if (s && !tp->bios_version_str)
|
||||
return -ENOMEM;
|
||||
if (!tp->bios_version_str)
|
||||
|
||||
/* Really ancient ThinkPad 240X will fail this, which is fine */
|
||||
if (!tpacpi_is_valid_fw_id(tp->bios_version_str, 'E'))
|
||||
return 0;
|
||||
|
||||
tp->bios_model = tp->bios_version_str[0]
|
||||
| (tp->bios_version_str[1] << 8);
|
||||
tp->bios_release = (tp->bios_version_str[4] << 8)
|
||||
| tp->bios_version_str[5];
|
||||
|
||||
/*
|
||||
* ThinkPad T23 or newer, A31 or newer, R50e or newer,
|
||||
@@ -7433,8 +7751,21 @@ static int __must_check __init get_thinkpad_model_data(
|
||||
tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
|
||||
if (!tp->ec_version_str)
|
||||
return -ENOMEM;
|
||||
tp->ec_model = ec_fw_string[0]
|
||||
| (ec_fw_string[1] << 8);
|
||||
|
||||
if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) {
|
||||
tp->ec_model = ec_fw_string[0]
|
||||
| (ec_fw_string[1] << 8);
|
||||
tp->ec_release = (ec_fw_string[4] << 8)
|
||||
| ec_fw_string[5];
|
||||
} else {
|
||||
printk(TPACPI_NOTICE
|
||||
"ThinkPad firmware release %s "
|
||||
"doesn't match the known patterns\n",
|
||||
ec_fw_string);
|
||||
printk(TPACPI_NOTICE
|
||||
"please report this to %s\n",
|
||||
TPACPI_MAIL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user