2006-10-03 01:11:06

by Lennart Poettering

[permalink] [raw]
Subject: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

From: Lennart Poettering <[email protected]>

A driver for the special features of MSI S270 laptops (and maybe other
MSI laptops). This driver implements a backlight device for
controlling LCD brightness (/sys/class/backlight/msi-laptop-bl/). In
addition it allows access to the WLAN and Bluetooth states through a
platform driver (/sys/devices/platform/msi-laptop-pf/).

Signed-off-by: Lennart Poettering <[email protected]>
---

This is the fourth version of this driver. Changes to the previous
version are mostly cosmetics: indentation, removal von Subversion
$Id$ and minor changes in the commenting style. These changes were
requested by Pavel Machek and Andrew Morton.

(Just for reference, the previous version of this patch is
available here: http://lkml.org/lkml/2006/8/20/219)

Applies to Len Brown's current linux-acpi tree.

Len, please merge!

Thanks,
Lennart

MAINTAINERS | 7 +
drivers/acpi/ec.c | 2
drivers/misc/Kconfig | 19 ++
drivers/misc/Makefile | 1
drivers/misc/msi-laptop.c | 383 +++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 412 insertions(+), 0 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 23348c0..0d99de9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1978,6 +1978,13 @@ M: [email protected]
L: [email protected]
S: Maintained

+MSI LAPTOP SUPPORT
+P: Lennart Poettering
+M: [email protected]
+L: https://tango.0pointer.de/mailman/listinfo/s270-linux
+W: http://0pointer.de/lennart/tchibo.html
+S: Maintained
+
MTRR AND SIMILAR SUPPORT [i386]
P: Richard Gooch
M: [email protected]
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index ae05e8c..e6d4b08 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -384,6 +384,8 @@ extern int ec_transaction(u8 command,
wdata_len, rdata, rdata_len);
}

+EXPORT_SYMBOL(ec_transaction);
+
static int acpi_ec_query(struct acpi_ec *ec, u8 *data)
{
int result;
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7fc692a..d25b0db 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -28,5 +28,24 @@ config IBM_ASM

If unsure, say N.

+config MSI_LAPTOP
+ tristate "MSI Laptop Extras"
+ depends on X86
+ depends on ACPI_EC
+ depends on BACKLIGHT_CLASS_DEVICE
+ ---help---
+ This is a driver for laptops built by MSI (MICRO-STAR
+ INTERNATIONAL):
+
+ MSI MegaBook S270 (MS-1013)
+ Cytron/TCM/Medion/Tchibo MD96100/SAM2000
+
+ It adds support for Bluetooth, WLAN and LCD brightness control.
+
+ More information about this driver is available at
+ <http://0pointer.de/lennart/tchibo.html>.
+
+ If you have an MSI S270 laptop, say Y or M here.
+
endmenu

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 19c2b85..1328c5f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,3 +5,4 @@ obj- := misc.o # Dummy rule to force bui

obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
+obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
diff --git a/drivers/misc/msi-laptop.c b/drivers/misc/msi-laptop.c
new file mode 100644
index 0000000..6b87b42
--- /dev/null
+++ b/drivers/misc/msi-laptop.c
@@ -0,0 +1,383 @@
+/*-*-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.4"
+
+#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);
+}
+
+static int get_lcd_level(void)
+{
+ u8 wdata = 0, rdata;
+ int result;
+
+ result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 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);
+ 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);
+ 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);
+}
+
+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);
+ 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_properties msibl_props = {
+ .owner = THIS_MODULE,
+ .get_brightness = bl_get_brightness,
+ .update_status = bl_update_status,
+ .max_brightness = MSI_LCD_LEVEL_MAX-1,
+};
+
+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, &msibl_props);
+ if (IS_ERR(msibl_device))
+ return PTR_ERR(msibl_device);
+
+ ret = platform_driver_register(&msipf_driver);
+ if (ret)
+ goto fail_backlight;
+
+ /* Register platform stuff */
+
+ msipf_device = platform_device_register_simple("msi-laptop-pf", -1, NULL, 0);
+ if (IS_ERR(msipf_device)) {
+ ret = PTR_ERR(msipf_device);
+ goto fail_platform_driver;
+ }
+
+ ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
+ if (ret)
+ goto fail_platform_device;
+
+ /* 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_device:
+
+ platform_device_unregister(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");
--
1.4.1.1


2006-10-03 01:48:51

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

On Tue, 3 Oct 2006 03:10:56 +0200
Lennart Poettering <[email protected]> wrote:

> From: Lennart Poettering <[email protected]>
>
> A driver for the special features of MSI S270 laptops (and maybe other
> MSI laptops). This driver implements a backlight device for
> controlling LCD brightness (/sys/class/backlight/msi-laptop-bl/). In
> addition it allows access to the WLAN and Bluetooth states through a
> platform driver (/sys/devices/platform/msi-laptop-pf/).
>
> ...
>

err, this driver has only about fifteen tab characters in it. The whole
thing is using lean-on-the-spacebar instead of hard tabs.

fix, please??

> +static ssize_t store_lcd_level(
> + struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {

No, kernel-style is

static ssize_t store_lcd_level(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{

or thereabouts.


2006-10-03 02:27:15

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

Hi Lennart,

On Monday 02 October 2006 21:10, Lennart Poettering wrote:
> +
> + msipf_device = platform_device_register_simple("msi-laptop-pf", -1, NULL, 0);
> + if (IS_ERR(msipf_device)) {
> + ret = PTR_ERR(msipf_device);
> + goto fail_platform_driver;
> + }
> +

Please do not use platform_device_register_simple, use platform_device_alloc
and platform_device_add instead (_simple will go away some day).

> + ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
> + if (ret)
> + goto fail_platform_device;
> +
> +
> + /* Enable automatic brightness control again */
> + if (auto_brightness != 2)
> + set_auto_brightness(1);
> +

What happens if auto_brightness is 2 but userspace messed up with it
through device's sysfs attribute? Overall brightness controll interface
(module vs. per-device) needs to be tightened up.

--
Dmitry

2006-10-04 01:28:35

by Lennart Poettering

[permalink] [raw]
Subject: Re: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

On Mon, 02.10.06 22:27, Dmitry Torokhov ([email protected]) wrote:

> > + ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
> > + if (ret)
> > + goto fail_platform_device;
> > +
> > +
> > + /* Enable automatic brightness control again */
> > + if (auto_brightness != 2)
> > + set_auto_brightness(1);
> > +
>
> What happens if auto_brightness is 2 but userspace messed up with it
> through device's sysfs attribute?

If auto_brightness is 2 we assume that the user doesn't want the
module to fiddle with the automatic brightness control
automatically. So we don't do it, neither when loading nor when
unloading the module. However, if the user wants to fiddle with the
setting through sysfs he may do so and we will not reset his changes
when unloading the module. This allows the user to do something like
this to disable the brightness control without having the the driver
loaded the whole time:

modprobe msi-laptop auto_brightness=2 && echo 0 > /sys/devices/platform/msi-laptop-pf/auto_brightness && modprobe -r msi-laptop

If auto_brightness is 1 or 0, we do as requested but reset the control
to the bootup default when unloading. (i.e. enable it again)

Sounds like a reasonable policy to me, doesn't it to you?

> Overall brightness controll interface (module vs. per-device) needs
> to be tightened up.

Hmm, I am sorry? I don't understand what you mean?

There's only a single device of this type available in a system at
maximum. Hence "per-device" and "per-module" is mostly the same here.

Lennart

--
Lennart Poettering; lennart [at] poettering [dot] net
ICQ# 11060553; GPG 0x1A015CC4; http://0pointer.net/lennart/

2006-10-04 01:37:33

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

On Tuesday 03 October 2006 21:28, Lennart Poettering wrote:
> On Mon, 02.10.06 22:27, Dmitry Torokhov ([email protected]) wrote:
>
> > > + ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
> > > + if (ret)
> > > + goto fail_platform_device;
> > > +
> > > +
> > > + /* Enable automatic brightness control again */
> > > + if (auto_brightness != 2)
> > > + set_auto_brightness(1);
> > > +
> >
> > What happens if auto_brightness is 2 but userspace messed up with it
> > through device's sysfs attribute?
>
> If auto_brightness is 2 we assume that the user doesn't want the
> module to fiddle with the automatic brightness control
> automatically. So we don't do it, neither when loading nor when
> unloading the module. However, if the user wants to fiddle with the
> setting through sysfs he may do so and we will not reset his changes
> when unloading the module. This allows the user to do something like
> this to disable the brightness control without having the the driver
> loaded the whole time:
>
> modprobe msi-laptop auto_brightness=2 && echo 0 > /sys/devices/platform/msi-laptop-pf/auto_brightness && modprobe -r msi-laptop
>
> If auto_brightness is 1 or 0, we do as requested but reset the control
> to the bootup default when unloading. (i.e. enable it again)

Normally drivers clean up after themselves as if they were never loaded,
taht is why I questioned partial cleanup.

--
Dmitry

2006-10-04 01:53:36

by Lennart Poettering

[permalink] [raw]
Subject: Re: [PATCH] misc,acpi,backlight: MSI S270 Laptop support

On Tue, 03.10.06 21:37, Dmitry Torokhov ([email protected]) wrote:

> > If auto_brightness is 2 we assume that the user doesn't want the
> > module to fiddle with the automatic brightness control
> > automatically. So we don't do it, neither when loading nor when
> > unloading the module. However, if the user wants to fiddle with the
> > setting through sysfs he may do so and we will not reset his changes
> > when unloading the module. This allows the user to do something like
> > this to disable the brightness control without having the the driver
> > loaded the whole time:
> >
> > modprobe msi-laptop auto_brightness=2 && echo 0 > /sys/devices/platform/msi-laptop-pf/auto_brightness && modprobe -r msi-laptop
> >
> > If auto_brightness is 1 or 0, we do as requested but reset the control
> > to the bootup default when unloading. (i.e. enable it again)
>
> Normally drivers clean up after themselves as if they were never loaded,
> taht is why I questioned partial cleanup.

Sure. But "normally" the user wouldn't bother to pass
auto_brightness=2 to the module in which case we *do* "clean up" after
ourselves and reenable automatic brightness control on module unload.

The special behaviour when auto_brightness=2 is passed is merely a
special feature for those who need it. It was useful to me and was
trivial to implement, and hence I did it.

Lennart

--
Lennart Poettering; lennart [at] poettering [dot] net
ICQ# 11060553; GPG 0x1A015CC4; http://0pointer.net/lennart/