linux-acpi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH,RFC]: acpi,backlight: MSI S270 - driver, second try
@ 2006-08-10 16:23 Lennart Poettering
  2006-08-17 15:21 ` Pavel Machek
  0 siblings, 1 reply; 3+ messages in thread
From: Lennart Poettering @ 2006-08-10 16:23 UTC (permalink / raw)
  To: len.brown; +Cc: linux-kernel, linux-acpi

From: Lennart Poettering <mzxreary@0pointer.de>

This is my second try of the MSI S270 laptop driver. On request of Len
Brown I changed the following things:

- Move driver from drivers/acpi/s270.c to drivers/misc/s270.c

- Remove /proc/acpi/s270 interface, replace it by a platform device
  /sys/devices/platform/s270pf/. This means: no procfs is touched
  anymore, all features are now accessible through /sys/.

This patch applies to 2.6.17 and requires the ACPI ec_transaction()
patch I posted earlier:

http://marc.theaimsgroup.com/?l=linux-acpi&m=115517193511970&w=2

Please comment and/or apply!

Lennart
Signed-off-by: Lennart Poettering <mzxreary@0pointer.de>

---
--- linux-source-2.6.17.orig/MAINTAINERS	2006-06-18 03:49:35.000000000 +0200
+++ linux-source-2.6.17/MAINTAINERS	2006-08-10 01:16:50.000000000 +0200
@@ -1882,6 +1882,13 @@ M:	rubini@ipvvis.unipv.it
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 
+MSI S270 LAPTOP SUPPORT
+P:	Lennart Poettering
+M:	mzxreary@0pointer.de
+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:	rgooch@atnf.csiro.au
--- linux-source-2.6.17.orig/drivers/misc/Kconfig	2006-06-18 03:49:35.000000000 +0200
+++ linux-source-2.6.17/drivers/misc/Kconfig	2006-08-10 17:45:46.000000000 +0200
@@ -28,5 +28,19 @@ config IBM_ASM
 
 	  If unsure, say N.
 
+config MSI_S270
+        tristate "MSI S270 Laptop Extras"
+        depends on X86
+        depends on ACPI_EC
+        depends on BACKLIGHT_CLASS_DEVICE
+        ---help---
+          This is a driver for MSI S270 laptops. 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
 
--- linux-source-2.6.17.orig/drivers/misc/Makefile	2006-06-18 03:49:35.000000000 +0200
+++ linux-source-2.6.17/drivers/misc/Makefile	2006-08-10 17:43:54.000000000 +0200
@@ -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_S270)     += s270.o
--- linux-source-2.6.17.orig/drivers/misc/s270.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-source-2.6.17/drivers/misc/s270.c	2006-08-10 18:05:22.000000000 +0200
@@ -0,0 +1,372 @@
+/*-*-linux-c-*-*/
+
+/* $Id: s270.c 109 2006-08-10 16:05:22Z lennart $ */
+
+/***
+  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.
+***/
+
+/*
+ * s270.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/s270pf/:
+ *
+ *   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/s270bl/.
+ *
+ * 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 S270_VERSION "0.2"
+
+#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 const uint8_t lcd_table[MSI_LCD_LEVEL_MAX] = {
+        0x00, 0x1f, 0x3e, 0x5d, 0x7c, 0x9b, 0xba, 0xd9, 0xf8
+};
+
+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] = lcd_table[level];
+        
+        return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0);
+}
+
+static int get_lcd_level(void) {
+        u8 wdata = 0, rdata;
+        int i, result;
+        
+        if ((result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1)) < 0)
+                return result;
+        
+        for (i = 0; i < MSI_LCD_LEVEL_MAX; i++)
+                if (lcd_table[i] == rdata)
+                        return i;
+        
+        return -EIO;
+}
+
+static int get_auto_brightness(void) {
+        u8 wdata = 4, rdata;
+        int result;
+        
+        if ((result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1)) < 0)
+                return result;
+
+        return !!(rdata & 8);
+}
+
+static int set_auto_brightness(int enable) {
+        u8 wdata[2], rdata;
+        int result;
+        
+        wdata[0] = 4;
+            
+        if ((result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1)) < 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;
+
+        if ((result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1)) < 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 s270bl_props = {
+        .owner		= THIS_MODULE,
+	.get_brightness = bl_get_brightness,
+        .update_status  = bl_update_status,
+	.max_brightness = MSI_LCD_LEVEL_MAX,
+};
+
+static struct backlight_device *s270bl_device;
+
+/*** Platform device ***/
+
+static ssize_t show_wlan(
+        struct device *dev,
+        struct device_attribute *attr,
+        char *buf) {
+
+        int ret, enabled;
+
+        if ((ret = get_wireless_state(&enabled, NULL)) < 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;
+
+        if ((ret = get_wireless_state(NULL, &enabled)) < 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;
+
+        if ((ret = get_lcd_level()) < 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 > 8))
+                return -EINVAL;
+
+        if ((ret = set_lcd_level(level)) < 0)
+                return ret;
+        
+        return count;
+}
+
+static ssize_t show_auto_brightness(
+        struct device *dev,
+        struct device_attribute *attr,
+        char *buf) {
+
+        int ret;
+
+        if ((ret = get_auto_brightness()) < 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;
+
+        if ((ret = set_auto_brightness(enable)) < 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 *s270pf_attributes[] = {
+	&dev_attr_lcd_level.attr,
+	&dev_attr_auto_brightness.attr,
+	&dev_attr_bluetooth.attr,
+	&dev_attr_wlan.attr,
+	NULL
+};
+
+static struct attribute_group s270pf_attribute_group = {
+	.attrs = s270pf_attributes
+};
+
+static struct platform_driver s270pf_driver = {
+	.driver = {
+		.name = "s270pf",
+		.owner = THIS_MODULE,
+	}
+};
+
+static struct platform_device *s270pf_device;
+
+/*** Initialization ***/
+
+static struct dmi_system_id __initdata s270_dmi_table[] = { {
+        .ident = "MSI S270",
+        .matches = {
+                DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
+                DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
+        }
+}, { } };
+
+
+static int __init s270_init(void) {
+        int ret;
+
+        if (acpi_disabled)
+                return -ENODEV;
+
+        if (!force && !dmi_check_system(s270_dmi_table))
+                return -ENODEV;
+
+        if (auto_brightness < 0 || auto_brightness > 2)
+                return -EINVAL;
+
+        /* Register backlight stuff */
+        
+        s270bl_device = backlight_device_register("s270bl", NULL, &s270bl_props);
+        if (IS_ERR(s270bl_device))
+                return PTR_ERR(s270bl_device);
+
+        if ((ret = platform_driver_register(&s270pf_driver)))
+                goto fail_backlight;
+
+        /* Register platform stuff */
+        
+        s270pf_device = platform_device_register_simple("s270pf", -1, NULL, 0);
+	if (IS_ERR(s270pf_device)) {
+		ret = PTR_ERR(s270pf_device);
+		goto fail_platform_driver;
+	}
+
+        if ((ret = sysfs_create_group(&s270pf_device->dev.kobj, &s270pf_attribute_group)))
+                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 "s270: driver "S270_VERSION" successfully loaded.\n");
+        
+        return 0;
+
+fail_platform_device:
+        
+        platform_device_unregister(s270pf_device);
+
+fail_platform_driver:
+
+        platform_driver_unregister(&s270pf_driver);
+        
+fail_backlight:
+
+        backlight_device_unregister(s270bl_device);
+        
+        return ret;
+}
+
+static void __exit s270_cleanup(void) {
+
+        sysfs_remove_group(&s270pf_device->dev.kobj, &s270pf_attribute_group);
+        platform_device_unregister(s270pf_device);
+        platform_driver_unregister(&s270pf_driver);
+        backlight_device_unregister(s270bl_device);
+
+        /* Enable automatic brightness control again */
+        if (auto_brightness != 2)
+                set_auto_brightness(1);     
+
+        printk(KERN_INFO "s270: driver unloaded.\n");
+}
+
+module_init(s270_init);
+module_exit(s270_cleanup);
+
+MODULE_AUTHOR("Lennart Poettering");
+MODULE_DESCRIPTION("MSI S270 Laptop Support");
+MODULE_VERSION(S270_VERSION);
+MODULE_LICENSE("GPL");

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH,RFC]: acpi,backlight: MSI S270 - driver, second try
  2006-08-10 16:23 [PATCH,RFC]: acpi,backlight: MSI S270 - driver, second try Lennart Poettering
@ 2006-08-17 15:21 ` Pavel Machek
  2006-08-17 16:03   ` Lennart Poettering
  0 siblings, 1 reply; 3+ messages in thread
From: Pavel Machek @ 2006-08-17 15:21 UTC (permalink / raw)
  To: Lennart Poettering; +Cc: len.brown, linux-kernel, linux-acpi

Hi!

> - Remove /proc/acpi/s270 interface, replace it by a platform device
>   /sys/devices/platform/s270pf/. This means: no procfs is touched
>   anymore, all features are now accessible through /sys/.
> 
> This patch applies to 2.6.17 and requires the ACPI ec_transaction()
> patch I posted earlier:
> 
> http://marc.theaimsgroup.com/?l=linux-acpi&m=115517193511970&w=2
> 
> Please comment and/or apply!

Looks ok to me...

> +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 const uint8_t lcd_table[MSI_LCD_LEVEL_MAX] = {
> +        0x00, 0x1f, 0x3e, 0x5d, 0x7c, 0x9b, 0xba, 0xd9, 0xf8
> +};

Can we get 0xf8 levels and simplify code while we are at it?

> +        if ((result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1)) < 0)
> +                return result;

Please split this into two lines.

result = ...;
if (result.....)


> +static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
> +static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);

So bluetooth and wlan basically mirror physical switch state? Should
we make these switches available through input subsystem one day?
-- 
Thanks for all the (sleeping) penguins.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH,RFC]: acpi,backlight: MSI S270 - driver, second try
  2006-08-17 15:21 ` Pavel Machek
@ 2006-08-17 16:03   ` Lennart Poettering
  0 siblings, 0 replies; 3+ messages in thread
From: Lennart Poettering @ 2006-08-17 16:03 UTC (permalink / raw)
  To: Pavel Machek; +Cc: len.brown, linux-kernel, linux-acpi

On Thu, 17.08.06 15:21, Pavel Machek (pavel@suse.cz) wrote:

> > This patch applies to 2.6.17 and requires the ACPI ec_transaction()
> > patch I posted earlier:
> > 
> > http://marc.theaimsgroup.com/?l=linux-acpi&m=115517193511970&w=2
> > 
> > Please comment and/or apply!
> 
> Looks ok to me...

Thanks!

> 
> > +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 const uint8_t lcd_table[MSI_LCD_LEVEL_MAX] = {
> > +        0x00, 0x1f, 0x3e, 0x5d, 0x7c, 0x9b, 0xba, 0xd9, 0xf8
> > +};
> 
> Can we get 0xf8 levels and simplify code while we are at it?

Unfortunately not. The hardware only supports 9 levels of
brightness. 

However, I guess I could multiply the brightness level with 31 instead
of using the translation table above. 

I will fix that.

> > +        if ((result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1)) < 0)
> > +                return result;
> 
> Please split this into two lines.
> 
> result = ...;
> if (result.....)

Will do.

> > +static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
> > +static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
> 
> So bluetooth and wlan basically mirror physical switch state? Should
> we make these switches available through input subsystem one day?

Yes. Actually I've already been discussing with Ivo how to extend the
rfkill API to tell userspace whether an rfkill device is actually for
Bluetooth, for wlan, for uwb and so on. I will update my S270 code as
soon as rfkill enters the kernel to support this new API properly. The
driver will then register two rfkill devices, one for Bluetooth and
one for WLAN.

Lennart

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

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2006-08-17 16:03 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-08-10 16:23 [PATCH,RFC]: acpi,backlight: MSI S270 - driver, second try Lennart Poettering
2006-08-17 15:21 ` Pavel Machek
2006-08-17 16:03   ` Lennart Poettering

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).