mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
00eb43a189
The ACPI EC that is used in MSI laptops knows some non-standard commands for changing the screen brighntess and a few other things, which are used by the msi-laptop.c driver. Unfortunately for these commands no GPE events for IBF and OBF are triggered. Since nowadays the EC code uses the ec_intr=1 mode by default, this causes these operations to timeout, although they don't fail. In result, all operations that you can do with the msi-laptop.c driver take more or less 1s to complete, which is awfully slow. In one of the more recent kernels (2.6.20?) the EC subsystem has been revamped. With that change the EC timeout has been increased. before that increase the MSI EC accesses were slow -- but not *that* slow, hence I took notice of this limitation of the MSI EC hardware only very recently. The standard EC operations on the MSI EC as defined in the ACPI spec support GPE events properly. The following patch adds a new argument "force_poll" to the ec_transaction() function (and friends). If set to 1, the function will poll for IBF/OBF even if ec_intr=1 is enabled. If set to 0 the current behaviour is used. The msi-laptop driver is modified to make use of this new flag, so that OBF/IBF is polled for the special MSI EC transactions -- but only for them. Signed-off-by: Lennart Poettering <mzxreary@0pointer.de> Acked-by: Alexey Starikovskiy <aystarik@gmail.com> Signed-off-by: Len Brown <len.brown@intel.com>
397 lines
8.8 KiB
C
397 lines
8.8 KiB
C
/*-*-linux-c-*-*/
|
|
|
|
/*
|
|
Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
* msi-laptop.c - MSI S270 laptop support. This laptop is sold under
|
|
* various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
|
|
*
|
|
* This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
|
|
*
|
|
* lcd_level - Screen brightness: contains a single integer in the
|
|
* range 0..8. (rw)
|
|
*
|
|
* auto_brightness - Enable automatic brightness control: contains
|
|
* either 0 or 1. If set to 1 the hardware adjusts the screen
|
|
* brightness automatically when the power cord is
|
|
* plugged/unplugged. (rw)
|
|
*
|
|
* wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
|
|
*
|
|
* bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
|
|
* Please note that this file is constantly 0 if no Bluetooth
|
|
* hardware is available. (ro)
|
|
*
|
|
* In addition to these platform device attributes the driver
|
|
* registers itself in the Linux backlight control subsystem and is
|
|
* available to userspace under /sys/class/backlight/msi-laptop-bl/.
|
|
*
|
|
* This driver might work on other laptops produced by MSI. If you
|
|
* want to try it you can pass force=1 as argument to the module which
|
|
* will force it to load even when the DMI data doesn't identify the
|
|
* laptop as MSI S270. YMMV.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/backlight.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/autoconf.h>
|
|
|
|
#define MSI_DRIVER_VERSION "0.5"
|
|
|
|
#define MSI_LCD_LEVEL_MAX 9
|
|
|
|
#define MSI_EC_COMMAND_WIRELESS 0x10
|
|
#define MSI_EC_COMMAND_LCD_LEVEL 0x11
|
|
|
|
static int force;
|
|
module_param(force, bool, 0);
|
|
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
|
|
|
|
static int auto_brightness;
|
|
module_param(auto_brightness, int, 0);
|
|
MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
|
|
|
|
/* Hardware access */
|
|
|
|
static int set_lcd_level(int level)
|
|
{
|
|
u8 buf[2];
|
|
|
|
if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
|
|
return -EINVAL;
|
|
|
|
buf[0] = 0x80;
|
|
buf[1] = (u8) (level*31);
|
|
|
|
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0, 1);
|
|
}
|
|
|
|
static int get_lcd_level(void)
|
|
{
|
|
u8 wdata = 0, rdata;
|
|
int result;
|
|
|
|
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
|
|
if (result < 0)
|
|
return result;
|
|
|
|
return (int) rdata / 31;
|
|
}
|
|
|
|
static int get_auto_brightness(void)
|
|
{
|
|
u8 wdata = 4, rdata;
|
|
int result;
|
|
|
|
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1, 1);
|
|
if (result < 0)
|
|
return result;
|
|
|
|
return !!(rdata & 8);
|
|
}
|
|
|
|
static int set_auto_brightness(int enable)
|
|
{
|
|
u8 wdata[2], rdata;
|
|
int result;
|
|
|
|
wdata[0] = 4;
|
|
|
|
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1, 1);
|
|
if (result < 0)
|
|
return result;
|
|
|
|
wdata[0] = 0x84;
|
|
wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
|
|
|
|
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0, 1);
|
|
}
|
|
|
|
static int get_wireless_state(int *wlan, int *bluetooth)
|
|
{
|
|
u8 wdata = 0, rdata;
|
|
int result;
|
|
|
|
result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1, 1);
|
|
if (result < 0)
|
|
return -1;
|
|
|
|
if (wlan)
|
|
*wlan = !!(rdata & 8);
|
|
|
|
if (bluetooth)
|
|
*bluetooth = !!(rdata & 128);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Backlight device stuff */
|
|
|
|
static int bl_get_brightness(struct backlight_device *b)
|
|
{
|
|
return get_lcd_level();
|
|
}
|
|
|
|
|
|
static int bl_update_status(struct backlight_device *b)
|
|
{
|
|
return set_lcd_level(b->props.brightness);
|
|
}
|
|
|
|
static struct backlight_ops msibl_ops = {
|
|
.get_brightness = bl_get_brightness,
|
|
.update_status = bl_update_status,
|
|
};
|
|
|
|
static struct backlight_device *msibl_device;
|
|
|
|
/* Platform device */
|
|
|
|
static ssize_t show_wlan(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
int ret, enabled;
|
|
|
|
ret = get_wireless_state(&enabled, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return sprintf(buf, "%i\n", enabled);
|
|
}
|
|
|
|
static ssize_t show_bluetooth(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
int ret, enabled;
|
|
|
|
ret = get_wireless_state(NULL, &enabled);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return sprintf(buf, "%i\n", enabled);
|
|
}
|
|
|
|
static ssize_t show_lcd_level(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
int ret;
|
|
|
|
ret = get_lcd_level();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return sprintf(buf, "%i\n", ret);
|
|
}
|
|
|
|
static ssize_t store_lcd_level(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
|
|
int level, ret;
|
|
|
|
if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX))
|
|
return -EINVAL;
|
|
|
|
ret = set_lcd_level(level);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t show_auto_brightness(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
|
|
int ret;
|
|
|
|
ret = get_auto_brightness();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return sprintf(buf, "%i\n", ret);
|
|
}
|
|
|
|
static ssize_t store_auto_brightness(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
|
|
int enable, ret;
|
|
|
|
if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
|
|
return -EINVAL;
|
|
|
|
ret = set_auto_brightness(enable);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
|
|
static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness);
|
|
static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
|
|
static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
|
|
|
|
static struct attribute *msipf_attributes[] = {
|
|
&dev_attr_lcd_level.attr,
|
|
&dev_attr_auto_brightness.attr,
|
|
&dev_attr_bluetooth.attr,
|
|
&dev_attr_wlan.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group msipf_attribute_group = {
|
|
.attrs = msipf_attributes
|
|
};
|
|
|
|
static struct platform_driver msipf_driver = {
|
|
.driver = {
|
|
.name = "msi-laptop-pf",
|
|
.owner = THIS_MODULE,
|
|
}
|
|
};
|
|
|
|
static struct platform_device *msipf_device;
|
|
|
|
/* Initialization */
|
|
|
|
static struct dmi_system_id __initdata msi_dmi_table[] = {
|
|
{
|
|
.ident = "MSI S270",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
|
|
}
|
|
},
|
|
{
|
|
.ident = "Medion MD96100",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
|
|
}
|
|
},
|
|
{ }
|
|
};
|
|
|
|
|
|
static int __init msi_init(void)
|
|
{
|
|
int ret;
|
|
|
|
if (acpi_disabled)
|
|
return -ENODEV;
|
|
|
|
if (!force && !dmi_check_system(msi_dmi_table))
|
|
return -ENODEV;
|
|
|
|
if (auto_brightness < 0 || auto_brightness > 2)
|
|
return -EINVAL;
|
|
|
|
/* Register backlight stuff */
|
|
|
|
msibl_device = backlight_device_register("msi-laptop-bl", NULL, NULL,
|
|
&msibl_ops);
|
|
if (IS_ERR(msibl_device))
|
|
return PTR_ERR(msibl_device);
|
|
|
|
msibl_device->props.max_brightness = MSI_LCD_LEVEL_MAX-1,
|
|
|
|
ret = platform_driver_register(&msipf_driver);
|
|
if (ret)
|
|
goto fail_backlight;
|
|
|
|
/* Register platform stuff */
|
|
|
|
msipf_device = platform_device_alloc("msi-laptop-pf", -1);
|
|
if (!msipf_device) {
|
|
ret = -ENOMEM;
|
|
goto fail_platform_driver;
|
|
}
|
|
|
|
ret = platform_device_add(msipf_device);
|
|
if (ret)
|
|
goto fail_platform_device1;
|
|
|
|
ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
|
if (ret)
|
|
goto fail_platform_device2;
|
|
|
|
/* Disable automatic brightness control by default because
|
|
* this module was probably loaded to do brightness control in
|
|
* software. */
|
|
|
|
if (auto_brightness != 2)
|
|
set_auto_brightness(auto_brightness);
|
|
|
|
printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n");
|
|
|
|
return 0;
|
|
|
|
fail_platform_device2:
|
|
|
|
platform_device_del(msipf_device);
|
|
|
|
fail_platform_device1:
|
|
|
|
platform_device_put(msipf_device);
|
|
|
|
fail_platform_driver:
|
|
|
|
platform_driver_unregister(&msipf_driver);
|
|
|
|
fail_backlight:
|
|
|
|
backlight_device_unregister(msibl_device);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit msi_cleanup(void)
|
|
{
|
|
|
|
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
|
|
platform_device_unregister(msipf_device);
|
|
platform_driver_unregister(&msipf_driver);
|
|
backlight_device_unregister(msibl_device);
|
|
|
|
/* Enable automatic brightness control again */
|
|
if (auto_brightness != 2)
|
|
set_auto_brightness(1);
|
|
|
|
printk(KERN_INFO "msi-laptop: driver unloaded.\n");
|
|
}
|
|
|
|
module_init(msi_init);
|
|
module_exit(msi_cleanup);
|
|
|
|
MODULE_AUTHOR("Lennart Poettering");
|
|
MODULE_DESCRIPTION("MSI Laptop Support");
|
|
MODULE_VERSION(MSI_DRIVER_VERSION);
|
|
MODULE_LICENSE("GPL");
|