diff mbox

New Samsung driver for backlight control.

Message ID 20090821233704.GA27715@kroah.com (mailing list archive)
State Not Applicable
Headers show

Commit Message

Greg KH Aug. 21, 2009, 11:37 p.m. UTC
On Sat, Aug 15, 2009 at 01:33:13PM +0100, Mike Lothian wrote:
> This is great news guys, hopefully I'll finally be able to control the
> screen brightness of my GM45 in my Samsung R510

Ok, as the testing with you and Jesse proved, your BIOS really is
reporting that you shouldn't be mucking around with "raw" pci config
read/write.

I've now rewritten the Samsung driver to talk through the BIOS, thanks
to some information that someone has forwarded on to me.

It would be great if you could test this patch out to see if it works
for your machine or not.

You will need to load the driver with the force=1 setting set, as it
does not have your DMI values in it.

If you do:
	modprobe samsung-laptop force=1
and let me know what the kernel log reports, that would be wonderful.

thanks,

greg k-h

---------------

From: Greg Kroah-Hartman <gregkh@suse.de>
Subject: Samsung laptop driver

This driver implements backlight controls for Samsung laptops that
currently do not have ACPI support for this control.

It has been tested on the N130 laptop and properly works there.

Many thanks to Dmitry Torokhov <dmitry.torokhov@gmail.com> for cleanups
and other suggestions on how to make the driver simpler.

Cc: Soeren Sonnenburg <bugreports@nn7.de>
Cc: Jérémie Huchet <jeremie@lamah.info>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

---
 drivers/platform/x86/Kconfig          |   12 
 drivers/platform/x86/Makefile         |    1 
 drivers/platform/x86/samsung-laptop.c |  420 ++++++++++++++++++++++++++++++++++
 3 files changed, 433 insertions(+)

Comments

Mike Lothian Aug. 22, 2009, 9:27 a.m. UTC | #1
2009/8/22 Greg KH <greg@kroah.com>:
> On Sat, Aug 15, 2009 at 01:33:13PM +0100, Mike Lothian wrote:
>> This is great news guys, hopefully I'll finally be able to control the
>> screen brightness of my GM45 in my Samsung R510
>
> Ok, as the testing with you and Jesse proved, your BIOS really is
> reporting that you shouldn't be mucking around with "raw" pci config
> read/write.
>
> I've now rewritten the Samsung driver to talk through the BIOS, thanks
> to some information that someone has forwarded on to me.
>
> It would be great if you could test this patch out to see if it works
> for your machine or not.
>
> You will need to load the driver with the force=1 setting set, as it
> does not have your DMI values in it.
>
> If you do:
>        modprobe samsung-laptop force=1
> and let me know what the kernel log reports, that would be wonderful.
>
> thanks,
>
> greg k-h
>
> ---------------
>
> From: Greg Kroah-Hartman <gregkh@suse.de>
> Subject: Samsung laptop driver
>
> This driver implements backlight controls for Samsung laptops that
> currently do not have ACPI support for this control.
>
> It has been tested on the N130 laptop and properly works there.
>
> Many thanks to Dmitry Torokhov <dmitry.torokhov@gmail.com> for cleanups
> and other suggestions on how to make the driver simpler.
>
> Cc: Soeren Sonnenburg <bugreports@nn7.de>
> Cc: Jérémie Huchet <jeremie@lamah.info>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
>
> ---
>  drivers/platform/x86/Kconfig          |   12
>  drivers/platform/x86/Makefile         |    1
>  drivers/platform/x86/samsung-laptop.c |  420 ++++++++++++++++++++++++++++++++++
>  3 files changed, 433 insertions(+)
>
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -425,4 +425,16 @@ config ACPI_TOSHIBA
>
>          If you have a legacy free Toshiba laptop (such as the Libretto L1
>          series), say Y.
> +
> +config SAMSUNG_LAPTOP
> +       tristate "Samsung Laptop driver"
> +       depends on BACKLIGHT_CLASS_DEVICE
> +       depends on DMI
> +       ---help---
> +         This driver adds support to control the backlight on a number of
> +         Samsung laptops, like the N130, and control for some of the LEDs
> +
> +         It will only be loaded on laptops that properly need it, so it is
> +         safe to say Y here.
> +
>  endif # X86_PLATFORM_DEVICES
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -20,3 +20,4 @@ obj-$(CONFIG_INTEL_MENLOW)    += intel_menl
>  obj-$(CONFIG_ACPI_WMI)         += wmi.o
>  obj-$(CONFIG_ACPI_ASUS)                += asus_acpi.o
>  obj-$(CONFIG_ACPI_TOSHIBA)     += toshiba_acpi.o
> +obj-$(CONFIG_SAMSUNG_LAPTOP)   += samsung-laptop.o
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-laptop.c
> @@ -0,0 +1,420 @@
> +/*
> + * Samsung N130 and NC120 Laptop driver
> + *
> + * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de)
> + * Copyright (C) 2009 Novell Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + */
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/backlight.h>
> +#include <linux/fb.h>
> +#include <linux/dmi.h>
> +#include <linux/platform_device.h>
> +
> +/*
> + * This driver is needed because a number of Samsung laptops do not hook
> + * their control settings through ACPI.  So we have to poke around in the
> + * BIOS to do things like brightness values, and "special" key controls.
> + */
> +
> +
> +/*
> + * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
> + * be reserved by the BIOS (which really doesn't make much sense), we tell
> + * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
> + */
> +#define MAX_BRIGHT     0x07
> +
> +/* get model returns 4 characters that describe the model of the laptop */
> +#define SABI_GET_MODEL                 0x04
> +
> +/* Brightness is 0 - 8, as described above.  Value 0 is for the BIOS to use */
> +#define SABI_GET_BRIGHTNESS            0x10
> +#define SABI_SET_BRIGHTNESS            0x11
> +
> +/* 0 is off, 1 is on, and 2 is a second user-defined key? */
> +#define SABI_GET_WIRELESS_BUTTON       0x12
> +#define SABI_SET_WIRELESS_BUTTON       0x13
> +
> +/* Temperature is returned in degress Celsius from what I can guess. */
> +#define SABI_GET_CPU_TEMP              0x29
> +
> +/* 0 is off, 1 is on.  Doesn't seem to work on a N130 for some reason */
> +#define SABI_GET_BACKLIGHT             0x2d
> +#define SABI_SET_BACKLIGHT             0x2e
> +
> +/*
> + * This is different
> + * There is 3 different modes here:
> + *   0 - off
> + *   1 - on
> + *   2 - max performance mode
> + * off is "normal" mode.
> + * on means that whatever the bios setting for etiquette mode, is enabled.  It
> + * seems that the BIOS can set either "auto" mode, or "slow" mode.  If "slow"
> + * mode is set, the fan turns off, and the cpu is throttled down to not cause
> + * the fan to turn on if at all possible.
> + * max performance means that the processor can be overclocked and run faster
> + * then is physically possible.  Ok, maybe not physically possible, but it is
> + * overclocked.  Funny that the system has a setting for this...
> + */
> +#define SABI_GET_ETIQUETTE_MODE                0x31
> +#define SABI_SET_ETIQUETTE_MODE                0x32
> +
> +/*
> + * I imagine that on some laptops there is a bluetooth switch, but I don't know
> + * what that looks like, or where it is in the BIOS address space
> + */
> +
> +
> +/*
> + * SABI HEADER in low memory (f0000)
> + * We need to poke through memory to find a signature in order to find the
> + * exact location of this structure.
> + */
> +struct sabi_header {
> +       u16 portNo;
> +       u8 ifaceFunc;
> +       u8 enMem;
> +       u8 reMem;
> +       u16 dataOffset;
> +       u16 dataSegment;
> +       u8 BIOSifver;
> +       u8 LauncherString;
> +} __attribute__((packed));
> +
> +/*
> + * The SABI interface that we use to write and read values from the system.
> + * It is found by looking at the dataOffset and dataSegment values in the sabi
> + * header structure
> + */
> +struct sabi_interface {
> +       u16 mainfunc;
> +       u16 subfunc;
> +       u8 complete;
> +       u8 retval[20];
> +} __attribute__((packed));
> +
> +/* Structure to get data back to the calling function */
> +struct sabi_retval {
> +       u8 retval[20];
> +};
> +
> +static struct sabi_header __iomem *sabi;
> +static struct sabi_interface __iomem *sabi_iface;
> +static void __iomem *f0000_segment;
> +static struct backlight_device *backlight_device;
> +static struct mutex sabi_mutex;
> +static struct platform_device *sdev;
> +
> +static int force;
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Disable the DMI check and forces the driver to be loaded");
> +
> +static int debug;
> +module_param(debug, bool, S_IRUGO | S_IWUSR);
> +MODULE_PARM_DESC(debug, "Debug enabled or not");
> +
> +static int sabi_get_command(u8 command, struct sabi_retval *sretval)
> +{
> +       int retval = 0;
> +
> +       mutex_lock(&sabi_mutex);
> +
> +       /* enable memory to be able to write to it */
> +       outb(readb(&sabi->enMem), readw(&sabi->portNo));
> +
> +       /* write out the command */
> +       writew(0x5843, &sabi_iface->mainfunc);
> +       writew(command, &sabi_iface->subfunc);
> +       writeb(0, &sabi_iface->complete);
> +       outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
> +
> +       /* sleep for a bit to let the command complete */
> +       msleep(100);
> +
> +       /* write protect memory to make it safe */
> +       outb(readb(&sabi->reMem), readw(&sabi->portNo));
> +
> +       /* see if the command actually succeeded */
> +       if (readb(&sabi_iface->complete) == 0xaa &&
> +           readb(&sabi_iface->retval[0]) != 0xff) {
> +               /*
> +                * It did!
> +                * Save off the data into a structure so the caller use it.
> +                * Right now we only care about the first 4 bytes,
> +                * I suppose there are commands that need more, but I don't
> +                * know about them.
> +                */
> +               sretval->retval[0] = readb(&sabi_iface->retval[0]);
> +               sretval->retval[1] = readb(&sabi_iface->retval[1]);
> +               sretval->retval[2] = readb(&sabi_iface->retval[2]);
> +               sretval->retval[3] = readb(&sabi_iface->retval[3]);
> +               goto exit;
> +       }
> +
> +       /* Something bad happened, so report it and error out */
> +       printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
> +               command, readb(&sabi_iface->complete),
> +               readb(&sabi_iface->retval[0]));
> +       retval = -EINVAL;
> +exit:
> +       mutex_unlock(&sabi_mutex);
> +       return retval;
> +
> +}
> +
> +static int sabi_set_command(u8 command, u8 data)
> +{
> +       int retval = 0;
> +
> +       mutex_lock(&sabi_mutex);
> +
> +       /* enable memory to be able to write to it */
> +       outb(readb(&sabi->enMem), readw(&sabi->portNo));
> +
> +       /* write out the command */
> +       writew(0x5843, &sabi_iface->mainfunc);
> +       writew(command, &sabi_iface->subfunc);
> +       writeb(0, &sabi_iface->complete);
> +       writeb(data, &sabi_iface->retval[0]);
> +       outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
> +
> +       /* sleep for a bit to let the command complete */
> +       msleep(100);
> +
> +       /* write protect memory to make it safe */
> +       outb(readb(&sabi->reMem), readw(&sabi->portNo));
> +
> +       /* see if the command actually succeeded */
> +       if (readb(&sabi_iface->complete) == 0xaa &&
> +           readb(&sabi_iface->retval[0]) != 0xff) {
> +               /* it did! */
> +               goto exit;
> +       }
> +
> +       /* Something bad happened, so report it and error out */
> +       printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
> +               command, readb(&sabi_iface->complete),
> +               readb(&sabi_iface->retval[0]));
> +       retval = -EINVAL;
> +exit:
> +       mutex_unlock(&sabi_mutex);
> +       return retval;
> +}
> +
> +static u8 read_brightness(void)
> +{
> +       struct sabi_retval sretval;
> +       int user_brightness = 0;
> +       int retval;
> +
> +       retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
> +       if (!retval)
> +               user_brightness = sretval.retval[0];
> +               if (user_brightness != 0)
> +                       --user_brightness;
> +       return user_brightness;
> +}
> +
> +static void set_brightness(u8 user_brightness)
> +{
> +       sabi_set_command(SABI_SET_BRIGHTNESS, user_brightness + 1);
> +}
> +
> +static int get_brightness(struct backlight_device *bd)
> +{
> +       return bd->props.brightness;
> +}
> +
> +static int update_status(struct backlight_device *bd)
> +{
> +       set_brightness(bd->props.brightness);
> +       return 0;
> +}
> +
> +static struct backlight_ops backlight_ops = {
> +       .get_brightness = get_brightness,
> +       .update_status  = update_status,
> +};
> +
> +static int __init dmi_check_cb(const struct dmi_system_id *id)
> +{
> +       printk(KERN_INFO KBUILD_MODNAME ": found laptop model '%s'\n",
> +               id->ident);
> +       return 0;
> +}
> +
> +static struct dmi_system_id __initdata samsung_dmi_table[] = {
> +       {
> +               .ident = "N120",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "N120"),
> +                       DMI_MATCH(DMI_BOARD_NAME, "N120"),
> +               },
> +               .callback = dmi_check_cb,
> +       },
> +       {
> +               .ident = "N130",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
> +                       DMI_MATCH(DMI_BOARD_NAME, "N130"),
> +               },
> +               .callback = dmi_check_cb,
> +       },
> +       { },
> +};
> +
> +
> +static int __init samsung_init(void)
> +{
> +       struct sabi_retval sretval;
> +       const char *testStr = "SwSmi@";
> +       void __iomem *memcheck;
> +       unsigned int ifaceP;
> +       int pStr;
> +       int loca;
> +       int retval;
> +
> +       mutex_init(&sabi_mutex);
> +
> +       if (!force && !dmi_check_system(samsung_dmi_table))
> +               return -ENODEV;
> +
> +       f0000_segment = ioremap(0xf0000, 0xffff);
> +       if (!f0000_segment) {
> +               printk(KERN_ERR "Can't map the segment at 0xf0000\n");
> +               return -EINVAL;
> +       }
> +
> +       /* Try to find the signature "SwSmi@" in memory to find the header */
> +       pStr = 0;
> +       memcheck = f0000_segment;
> +       for (loca = 0; loca < 0xffff; loca++) {
> +               char temp = readb(memcheck + loca);
> +
> +               if (temp == testStr[pStr]) {
> +                       if (pStr == 5)
> +                               break;
> +                       ++pStr;
> +               } else {
> +                       pStr = 0;
> +               }
> +       }
> +       if (loca == 0xffff) {
> +               printk(KERN_INFO "This computer does not support SABI\n");
> +               goto error_no_signature;
> +               }
> +
> +       /* point to the SMI port Number */
> +       loca += 1;
> +       sabi = (struct sabi_header __iomem *)(loca + memcheck);
> +       if (!sabi) {
> +               printk(KERN_ERR "Can't remap %p\n", loca + memcheck);
> +               goto exit;
> +       }
> +
> +       printk(KERN_INFO "This computer supports SABI==%x\n", loca + 0xf0000 - 6);
> +       printk(KERN_INFO "SABI header:\n");
> +       printk(KERN_INFO " SMI Port Number = 0x%04x\n", readw(&sabi->portNo));
> +       printk(KERN_INFO " SMI Interface Function = 0x%02x\n", readb(&sabi->ifaceFunc));
> +       printk(KERN_INFO " SMI enable memory buffer = 0x%02x\n", readb(&sabi->enMem));
> +       printk(KERN_INFO " SMI restore memory buffer = 0x%02x\n", readb(&sabi->reMem));
> +       printk(KERN_INFO " SABI data offset = 0x%04x\n", readw(&sabi->dataOffset));
> +       printk(KERN_INFO " SABI data segment = 0x%04x\n", readw(&sabi->dataSegment));
> +       printk(KERN_INFO " BIOS interface version = 0x%02x\n", readb(&sabi->BIOSifver));
> +       printk(KERN_INFO " KBD Launcher string = 0x%02x\n", readb(&sabi->LauncherString));
> +
> +       /* Get a pointer to the SABI Interface */
> +       ifaceP = (readw(&sabi->dataSegment) & 0x0ffff) << 4;
> +       ifaceP += readw(&sabi->dataOffset) & 0x0ffff;
> +       sabi_iface = (struct sabi_interface __iomem *)ioremap(ifaceP, 16);
> +       if (!sabi_iface) {
> +               printk(KERN_ERR "Can't remap %x\n", ifaceP);
> +               goto exit;
> +       }
> +       printk(KERN_INFO "SABI Interface = %p\n", sabi_iface);
> +
> +       retval = sabi_get_command(SABI_GET_MODEL, &sretval);
> +       if (!retval) {
> +               printk(KERN_INFO "Model Name %c%c%c%c\n",
> +                       sretval.retval[0],
> +                       sretval.retval[1],
> +                       sretval.retval[2],
> +                       sretval.retval[3]);
> +       }
> +
> +       retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
> +       if (!retval)
> +               printk("backlight = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_WIRELESS_BUTTON, &sretval);
> +       if (!retval)
> +               printk("wireless button = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_BRIGHTNESS, &sretval);
> +       if (!retval)
> +               printk("brightness = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_ETIQUETTE_MODE, &sretval);
> +       if (!retval)
> +               printk("etiquette mode = 0x%02x\n", sretval.retval[0]);
> +       retval = sabi_get_command(SABI_GET_CPU_TEMP, &sretval);
> +       if (!retval)
> +               printk("cpu temp = 0x%02x\n", sretval.retval[0]);
> +
> +       /* knock up a platform device to hang stuff off of */
> +       sdev = platform_device_register_simple("samsung", -1, NULL, 0);
> +       if (IS_ERR(sdev))
> +               goto error_no_platform;
> +
> +       /* create a backlight device to talk to this one */
> +       backlight_device = backlight_device_register("samsung", &sdev->dev,
> +                                                    NULL, &backlight_ops);
> +       if (IS_ERR(backlight_device))
> +               goto error_no_backlight;
> +
> +       backlight_device->props.max_brightness = MAX_BRIGHT;
> +       backlight_device->props.brightness = read_brightness();
> +       backlight_device->props.power = FB_BLANK_UNBLANK;
> +       backlight_update_status(backlight_device);
> +
> +exit:
> +       return 0;
> +
> +error_no_backlight:
> +       platform_device_unregister(sdev);
> +
> +error_no_platform:
> +       iounmap(sabi_iface);
> +
> +error_no_signature:
> +       iounmap(f0000_segment);
> +       return -EINVAL;
> +}
> +
> +static void __exit samsung_exit(void)
> +{
> +       backlight_device_unregister(backlight_device);
> +       iounmap(sabi_iface);
> +       iounmap(f0000_segment);
> +       platform_device_unregister(sdev);
> +}
> +
> +module_init(samsung_init);
> +module_exit(samsung_exit);
> +
> +MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
> +MODULE_DESCRIPTION("Samsung Backlight driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN120:*:rnN120:*");
> +MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN130:*:rnN130:*");
>

I can confirm that it works however it does have different behaviour

At the lowest setting the backlight is still on and the screen even
though dull is still readable with the previous patch the screen was
black

This is what it says in my dmesg:

This computer supports SABI==f494d
SABI header:
 SMI Port Number = 0x00b2
 SMI Interface Function = 0xc0
 SMI enable memory buffer = 0xc1
 SMI restore memory buffer = 0xc2
 SABI data offset = 0x0f00
 SABI data segment = 0xdf01
 BIOS interface version = 0x32
 KBD Launcher string = 0x53
SABI Interface = ffff8800000dff10
Model Name R510
backlight = 0x01
wireless button = 0x01
brightness = 0x08
etiquette mode = 0x00
cpu temp = 0x45

Do you have any idea why my brightness buttons aren't working correctly?

Thanks for you help with this

Mike
Mike Lothian Aug. 22, 2009, 9:41 a.m. UTC | #2
2009/8/22 Greg KH <greg@kroah.com>:
> On Sat, Aug 15, 2009 at 01:33:13PM +0100, Mike Lothian wrote:
>> This is great news guys, hopefully I'll finally be able to control the
>> screen brightness of my GM45 in my Samsung R510
>
> Ok, as the testing with you and Jesse proved, your BIOS really is
> reporting that you shouldn't be mucking around with "raw" pci config
> read/write.
>
> I've now rewritten the Samsung driver to talk through the BIOS, thanks
> to some information that someone has forwarded on to me.
>
> It would be great if you could test this patch out to see if it works
> for your machine or not.
>
> You will need to load the driver with the force=1 setting set, as it
> does not have your DMI values in it.
>
> If you do:
>        modprobe samsung-laptop force=1
> and let me know what the kernel log reports, that would be wonderful.
>
> thanks,
>
> greg k-h
>
> ---------------
>
> From: Greg Kroah-Hartman <gregkh@suse.de>
> Subject: Samsung laptop driver
>
> This driver implements backlight controls for Samsung laptops that
> currently do not have ACPI support for this control.
>
> It has been tested on the N130 laptop and properly works there.
>
> Many thanks to Dmitry Torokhov <dmitry.torokhov@gmail.com> for cleanups
> and other suggestions on how to make the driver simpler.
>
> Cc: Soeren Sonnenburg <bugreports@nn7.de>
> Cc: Jérémie Huchet <jeremie@lamah.info>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
>
> ---
>  drivers/platform/x86/Kconfig          |   12
>  drivers/platform/x86/Makefile         |    1
>  drivers/platform/x86/samsung-laptop.c |  420 ++++++++++++++++++++++++++++++++++
>  3 files changed, 433 insertions(+)
>
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -425,4 +425,16 @@ config ACPI_TOSHIBA
>
>          If you have a legacy free Toshiba laptop (such as the Libretto L1
>          series), say Y.
> +
> +config SAMSUNG_LAPTOP
> +       tristate "Samsung Laptop driver"
> +       depends on BACKLIGHT_CLASS_DEVICE
> +       depends on DMI
> +       ---help---
> +         This driver adds support to control the backlight on a number of
> +         Samsung laptops, like the N130, and control for some of the LEDs
> +
> +         It will only be loaded on laptops that properly need it, so it is
> +         safe to say Y here.
> +
>  endif # X86_PLATFORM_DEVICES
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -20,3 +20,4 @@ obj-$(CONFIG_INTEL_MENLOW)    += intel_menl
>  obj-$(CONFIG_ACPI_WMI)         += wmi.o
>  obj-$(CONFIG_ACPI_ASUS)                += asus_acpi.o
>  obj-$(CONFIG_ACPI_TOSHIBA)     += toshiba_acpi.o
> +obj-$(CONFIG_SAMSUNG_LAPTOP)   += samsung-laptop.o
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-laptop.c
> @@ -0,0 +1,420 @@
> +/*
> + * Samsung N130 and NC120 Laptop driver
> + *
> + * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de)
> + * Copyright (C) 2009 Novell Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + */
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/backlight.h>
> +#include <linux/fb.h>
> +#include <linux/dmi.h>
> +#include <linux/platform_device.h>
> +
> +/*
> + * This driver is needed because a number of Samsung laptops do not hook
> + * their control settings through ACPI.  So we have to poke around in the
> + * BIOS to do things like brightness values, and "special" key controls.
> + */
> +
> +
> +/*
> + * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
> + * be reserved by the BIOS (which really doesn't make much sense), we tell
> + * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
> + */
> +#define MAX_BRIGHT     0x07
> +
> +/* get model returns 4 characters that describe the model of the laptop */
> +#define SABI_GET_MODEL                 0x04
> +
> +/* Brightness is 0 - 8, as described above.  Value 0 is for the BIOS to use */
> +#define SABI_GET_BRIGHTNESS            0x10
> +#define SABI_SET_BRIGHTNESS            0x11
> +
> +/* 0 is off, 1 is on, and 2 is a second user-defined key? */
> +#define SABI_GET_WIRELESS_BUTTON       0x12
> +#define SABI_SET_WIRELESS_BUTTON       0x13
> +
> +/* Temperature is returned in degress Celsius from what I can guess. */
> +#define SABI_GET_CPU_TEMP              0x29
> +
> +/* 0 is off, 1 is on.  Doesn't seem to work on a N130 for some reason */
> +#define SABI_GET_BACKLIGHT             0x2d
> +#define SABI_SET_BACKLIGHT             0x2e
> +
> +/*
> + * This is different
> + * There is 3 different modes here:
> + *   0 - off
> + *   1 - on
> + *   2 - max performance mode
> + * off is "normal" mode.
> + * on means that whatever the bios setting for etiquette mode, is enabled.  It
> + * seems that the BIOS can set either "auto" mode, or "slow" mode.  If "slow"
> + * mode is set, the fan turns off, and the cpu is throttled down to not cause
> + * the fan to turn on if at all possible.
> + * max performance means that the processor can be overclocked and run faster
> + * then is physically possible.  Ok, maybe not physically possible, but it is
> + * overclocked.  Funny that the system has a setting for this...
> + */
> +#define SABI_GET_ETIQUETTE_MODE                0x31
> +#define SABI_SET_ETIQUETTE_MODE                0x32
> +
> +/*
> + * I imagine that on some laptops there is a bluetooth switch, but I don't know
> + * what that looks like, or where it is in the BIOS address space
> + */
> +
> +
> +/*
> + * SABI HEADER in low memory (f0000)
> + * We need to poke through memory to find a signature in order to find the
> + * exact location of this structure.
> + */
> +struct sabi_header {
> +       u16 portNo;
> +       u8 ifaceFunc;
> +       u8 enMem;
> +       u8 reMem;
> +       u16 dataOffset;
> +       u16 dataSegment;
> +       u8 BIOSifver;
> +       u8 LauncherString;
> +} __attribute__((packed));
> +
> +/*
> + * The SABI interface that we use to write and read values from the system.
> + * It is found by looking at the dataOffset and dataSegment values in the sabi
> + * header structure
> + */
> +struct sabi_interface {
> +       u16 mainfunc;
> +       u16 subfunc;
> +       u8 complete;
> +       u8 retval[20];
> +} __attribute__((packed));
> +
> +/* Structure to get data back to the calling function */
> +struct sabi_retval {
> +       u8 retval[20];
> +};
> +
> +static struct sabi_header __iomem *sabi;
> +static struct sabi_interface __iomem *sabi_iface;
> +static void __iomem *f0000_segment;
> +static struct backlight_device *backlight_device;
> +static struct mutex sabi_mutex;
> +static struct platform_device *sdev;
> +
> +static int force;
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Disable the DMI check and forces the driver to be loaded");
> +
> +static int debug;
> +module_param(debug, bool, S_IRUGO | S_IWUSR);
> +MODULE_PARM_DESC(debug, "Debug enabled or not");
> +
> +static int sabi_get_command(u8 command, struct sabi_retval *sretval)
> +{
> +       int retval = 0;
> +
> +       mutex_lock(&sabi_mutex);
> +
> +       /* enable memory to be able to write to it */
> +       outb(readb(&sabi->enMem), readw(&sabi->portNo));
> +
> +       /* write out the command */
> +       writew(0x5843, &sabi_iface->mainfunc);
> +       writew(command, &sabi_iface->subfunc);
> +       writeb(0, &sabi_iface->complete);
> +       outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
> +
> +       /* sleep for a bit to let the command complete */
> +       msleep(100);
> +
> +       /* write protect memory to make it safe */
> +       outb(readb(&sabi->reMem), readw(&sabi->portNo));
> +
> +       /* see if the command actually succeeded */
> +       if (readb(&sabi_iface->complete) == 0xaa &&
> +           readb(&sabi_iface->retval[0]) != 0xff) {
> +               /*
> +                * It did!
> +                * Save off the data into a structure so the caller use it.
> +                * Right now we only care about the first 4 bytes,
> +                * I suppose there are commands that need more, but I don't
> +                * know about them.
> +                */
> +               sretval->retval[0] = readb(&sabi_iface->retval[0]);
> +               sretval->retval[1] = readb(&sabi_iface->retval[1]);
> +               sretval->retval[2] = readb(&sabi_iface->retval[2]);
> +               sretval->retval[3] = readb(&sabi_iface->retval[3]);
> +               goto exit;
> +       }
> +
> +       /* Something bad happened, so report it and error out */
> +       printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
> +               command, readb(&sabi_iface->complete),
> +               readb(&sabi_iface->retval[0]));
> +       retval = -EINVAL;
> +exit:
> +       mutex_unlock(&sabi_mutex);
> +       return retval;
> +
> +}
> +
> +static int sabi_set_command(u8 command, u8 data)
> +{
> +       int retval = 0;
> +
> +       mutex_lock(&sabi_mutex);
> +
> +       /* enable memory to be able to write to it */
> +       outb(readb(&sabi->enMem), readw(&sabi->portNo));
> +
> +       /* write out the command */
> +       writew(0x5843, &sabi_iface->mainfunc);
> +       writew(command, &sabi_iface->subfunc);
> +       writeb(0, &sabi_iface->complete);
> +       writeb(data, &sabi_iface->retval[0]);
> +       outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
> +
> +       /* sleep for a bit to let the command complete */
> +       msleep(100);
> +
> +       /* write protect memory to make it safe */
> +       outb(readb(&sabi->reMem), readw(&sabi->portNo));
> +
> +       /* see if the command actually succeeded */
> +       if (readb(&sabi_iface->complete) == 0xaa &&
> +           readb(&sabi_iface->retval[0]) != 0xff) {
> +               /* it did! */
> +               goto exit;
> +       }
> +
> +       /* Something bad happened, so report it and error out */
> +       printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
> +               command, readb(&sabi_iface->complete),
> +               readb(&sabi_iface->retval[0]));
> +       retval = -EINVAL;
> +exit:
> +       mutex_unlock(&sabi_mutex);
> +       return retval;
> +}
> +
> +static u8 read_brightness(void)
> +{
> +       struct sabi_retval sretval;
> +       int user_brightness = 0;
> +       int retval;
> +
> +       retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
> +       if (!retval)
> +               user_brightness = sretval.retval[0];
> +               if (user_brightness != 0)
> +                       --user_brightness;
> +       return user_brightness;
> +}
> +
> +static void set_brightness(u8 user_brightness)
> +{
> +       sabi_set_command(SABI_SET_BRIGHTNESS, user_brightness + 1);
> +}
> +
> +static int get_brightness(struct backlight_device *bd)
> +{
> +       return bd->props.brightness;
> +}
> +
> +static int update_status(struct backlight_device *bd)
> +{
> +       set_brightness(bd->props.brightness);
> +       return 0;
> +}
> +
> +static struct backlight_ops backlight_ops = {
> +       .get_brightness = get_brightness,
> +       .update_status  = update_status,
> +};
> +
> +static int __init dmi_check_cb(const struct dmi_system_id *id)
> +{
> +       printk(KERN_INFO KBUILD_MODNAME ": found laptop model '%s'\n",
> +               id->ident);
> +       return 0;
> +}
> +
> +static struct dmi_system_id __initdata samsung_dmi_table[] = {
> +       {
> +               .ident = "N120",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "N120"),
> +                       DMI_MATCH(DMI_BOARD_NAME, "N120"),
> +               },
> +               .callback = dmi_check_cb,
> +       },
> +       {
> +               .ident = "N130",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
> +                       DMI_MATCH(DMI_BOARD_NAME, "N130"),
> +               },
> +               .callback = dmi_check_cb,
> +       },
> +       { },
> +};
> +
> +
> +static int __init samsung_init(void)
> +{
> +       struct sabi_retval sretval;
> +       const char *testStr = "SwSmi@";
> +       void __iomem *memcheck;
> +       unsigned int ifaceP;
> +       int pStr;
> +       int loca;
> +       int retval;
> +
> +       mutex_init(&sabi_mutex);
> +
> +       if (!force && !dmi_check_system(samsung_dmi_table))
> +               return -ENODEV;
> +
> +       f0000_segment = ioremap(0xf0000, 0xffff);
> +       if (!f0000_segment) {
> +               printk(KERN_ERR "Can't map the segment at 0xf0000\n");
> +               return -EINVAL;
> +       }
> +
> +       /* Try to find the signature "SwSmi@" in memory to find the header */
> +       pStr = 0;
> +       memcheck = f0000_segment;
> +       for (loca = 0; loca < 0xffff; loca++) {
> +               char temp = readb(memcheck + loca);
> +
> +               if (temp == testStr[pStr]) {
> +                       if (pStr == 5)
> +                               break;
> +                       ++pStr;
> +               } else {
> +                       pStr = 0;
> +               }
> +       }
> +       if (loca == 0xffff) {
> +               printk(KERN_INFO "This computer does not support SABI\n");
> +               goto error_no_signature;
> +               }
> +
> +       /* point to the SMI port Number */
> +       loca += 1;
> +       sabi = (struct sabi_header __iomem *)(loca + memcheck);
> +       if (!sabi) {
> +               printk(KERN_ERR "Can't remap %p\n", loca + memcheck);
> +               goto exit;
> +       }
> +
> +       printk(KERN_INFO "This computer supports SABI==%x\n", loca + 0xf0000 - 6);
> +       printk(KERN_INFO "SABI header:\n");
> +       printk(KERN_INFO " SMI Port Number = 0x%04x\n", readw(&sabi->portNo));
> +       printk(KERN_INFO " SMI Interface Function = 0x%02x\n", readb(&sabi->ifaceFunc));
> +       printk(KERN_INFO " SMI enable memory buffer = 0x%02x\n", readb(&sabi->enMem));
> +       printk(KERN_INFO " SMI restore memory buffer = 0x%02x\n", readb(&sabi->reMem));
> +       printk(KERN_INFO " SABI data offset = 0x%04x\n", readw(&sabi->dataOffset));
> +       printk(KERN_INFO " SABI data segment = 0x%04x\n", readw(&sabi->dataSegment));
> +       printk(KERN_INFO " BIOS interface version = 0x%02x\n", readb(&sabi->BIOSifver));
> +       printk(KERN_INFO " KBD Launcher string = 0x%02x\n", readb(&sabi->LauncherString));
> +
> +       /* Get a pointer to the SABI Interface */
> +       ifaceP = (readw(&sabi->dataSegment) & 0x0ffff) << 4;
> +       ifaceP += readw(&sabi->dataOffset) & 0x0ffff;
> +       sabi_iface = (struct sabi_interface __iomem *)ioremap(ifaceP, 16);
> +       if (!sabi_iface) {
> +               printk(KERN_ERR "Can't remap %x\n", ifaceP);
> +               goto exit;
> +       }
> +       printk(KERN_INFO "SABI Interface = %p\n", sabi_iface);
> +
> +       retval = sabi_get_command(SABI_GET_MODEL, &sretval);
> +       if (!retval) {
> +               printk(KERN_INFO "Model Name %c%c%c%c\n",
> +                       sretval.retval[0],
> +                       sretval.retval[1],
> +                       sretval.retval[2],
> +                       sretval.retval[3]);
> +       }
> +
> +       retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
> +       if (!retval)
> +               printk("backlight = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_WIRELESS_BUTTON, &sretval);
> +       if (!retval)
> +               printk("wireless button = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_BRIGHTNESS, &sretval);
> +       if (!retval)
> +               printk("brightness = 0x%02x\n", sretval.retval[0]);
> +
> +       retval = sabi_get_command(SABI_GET_ETIQUETTE_MODE, &sretval);
> +       if (!retval)
> +               printk("etiquette mode = 0x%02x\n", sretval.retval[0]);
> +       retval = sabi_get_command(SABI_GET_CPU_TEMP, &sretval);
> +       if (!retval)
> +               printk("cpu temp = 0x%02x\n", sretval.retval[0]);
> +
> +       /* knock up a platform device to hang stuff off of */
> +       sdev = platform_device_register_simple("samsung", -1, NULL, 0);
> +       if (IS_ERR(sdev))
> +               goto error_no_platform;
> +
> +       /* create a backlight device to talk to this one */
> +       backlight_device = backlight_device_register("samsung", &sdev->dev,
> +                                                    NULL, &backlight_ops);
> +       if (IS_ERR(backlight_device))
> +               goto error_no_backlight;
> +
> +       backlight_device->props.max_brightness = MAX_BRIGHT;
> +       backlight_device->props.brightness = read_brightness();
> +       backlight_device->props.power = FB_BLANK_UNBLANK;
> +       backlight_update_status(backlight_device);
> +
> +exit:
> +       return 0;
> +
> +error_no_backlight:
> +       platform_device_unregister(sdev);
> +
> +error_no_platform:
> +       iounmap(sabi_iface);
> +
> +error_no_signature:
> +       iounmap(f0000_segment);
> +       return -EINVAL;
> +}
> +
> +static void __exit samsung_exit(void)
> +{
> +       backlight_device_unregister(backlight_device);
> +       iounmap(sabi_iface);
> +       iounmap(f0000_segment);
> +       platform_device_unregister(sdev);
> +}
> +
> +module_init(samsung_init);
> +module_exit(samsung_exit);
> +
> +MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
> +MODULE_DESCRIPTION("Samsung Backlight driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN120:*:rnN120:*");
> +MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN130:*:rnN130:*");
>

Also the previous driver felt like it had finer grained control I also
think the maximum brightness setting was brighter too

I'll play around some more and see if this is the case

Mike
Matthew Garrett Aug. 24, 2009, 10:10 a.m. UTC | #3
On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote:

> Do you have any idea why my brightness buttons aren't working correctly?

The previous code would have let you set the brightness to the entire 
range supported by the GPU, which may be outside the specs of your 
backlight. The new one limits it to the range that the hardware is 
designed to use.
Mike Lothian Aug. 26, 2009, 7:34 a.m. UTC | #4
2009/8/24 Matthew Garrett <mjg59@srcf.ucam.org>:
> On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote:
>
>> Do you have any idea why my brightness buttons aren't working correctly?
>
> The previous code would have let you set the brightness to the entire
> range supported by the GPU, which may be outside the specs of your
> backlight. The new one limits it to the range that the hardware is
> designed to use.
>
> --
> Matthew Garrett | mjg59@srcf.ucam.org
>

If it's the BIOS that's telling the driver what's the limits are,
we've already established my BIOS is garbage, perhaps this isn't the
most reliable source of information

Now for the line I cringe at - under Vista 32bit (the only OS to
control the backlight before your patches) has full control of the
backlight from it's brightest setting to switched off

Being able to switch the backlight off through this control has a lot
of power saving advantages

I'd be greatful for a better solution in this case or at a worse case
scenario including both drivers

Cheers

Mike
Greg KH Aug. 26, 2009, 4:34 p.m. UTC | #5
On Sat, Aug 22, 2009 at 10:41:15AM +0100, Mike Lothian wrote:
> 
> Also the previous driver felt like it had finer grained control I also
> think the maximum brightness setting was brighter too

Yes, it did have finer granularity (0-255), vs. 0-7.

The brightest value should be identical, but 0 will not turn off the
light, that is on purpose.  Turns out that people don't like their
screens to be turned off when booting :)

thanks,

greg k-h
Greg KH Aug. 26, 2009, 4:36 p.m. UTC | #6
On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote:
> I can confirm that it works however it does have different behaviour
> 
> At the lowest setting the backlight is still on and the screen even
> though dull is still readable with the previous patch the screen was
> black
> 
> This is what it says in my dmesg:
> 
> This computer supports SABI==f494d
> SABI header:
>  SMI Port Number = 0x00b2
>  SMI Interface Function = 0xc0
>  SMI enable memory buffer = 0xc1
>  SMI restore memory buffer = 0xc2
>  SABI data offset = 0x0f00
>  SABI data segment = 0xdf01
>  BIOS interface version = 0x32
>  KBD Launcher string = 0x53
> SABI Interface = ffff8800000dff10
> Model Name R510
> backlight = 0x01
> wireless button = 0x01
> brightness = 0x08
> etiquette mode = 0x00
> cpu temp = 0x45

Thanks.

Can you provide me the output of:
	grep . /sys/class/dmi/id/*

So I can add this laptop to the driver?

> Do you have any idea why my brightness buttons aren't working correctly?

See the other responses, they are working "correctly" now, a 0 setting
will not turn off the light, as it is not supposed to.

thanks,

greg k-h
Greg KH Aug. 26, 2009, 4:37 p.m. UTC | #7
On Wed, Aug 26, 2009 at 08:34:10AM +0100, Mike Lothian wrote:
> 2009/8/24 Matthew Garrett <mjg59@srcf.ucam.org>:
> > On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote:
> >
> >> Do you have any idea why my brightness buttons aren't working correctly?
> >
> > The previous code would have let you set the brightness to the entire
> > range supported by the GPU, which may be outside the specs of your
> > backlight. The new one limits it to the range that the hardware is
> > designed to use.
> >
> > --
> > Matthew Garrett | mjg59@srcf.ucam.org
> >
> 
> If it's the BIOS that's telling the driver what's the limits are,
> we've already established my BIOS is garbage, perhaps this isn't the
> most reliable source of information
> 
> Now for the line I cringe at - under Vista 32bit (the only OS to
> control the backlight before your patches) has full control of the
> backlight from it's brightest setting to switched off
> 
> Being able to switch the backlight off through this control has a lot
> of power saving advantages

Yes, I need to figure out how to turn the light off "properly", am still
working on that.  Windows is _heavily_ patched by Samsung to use this
interface (you will notice a sabi.dll somewhere on that box that handles
all of this "magic").

Maybe someday Samsung will switch to using ACPI for this kind of thing,
which is the correct solution.

thanks,

greg k-h
Mike Lothian Aug. 26, 2009, 9:53 p.m. UTC | #8
tau / # grep . /sys/class/dmi/id/*
/sys/class/dmi/id/bios_date:09/26/2008
/sys/class/dmi/id/bios_vendor:Phoenix Technologies Ltd.
/sys/class/dmi/id/bios_version:07LI.MP00.20080926.SCY
/sys/class/dmi/id/board_name:R510/P510
/sys/class/dmi/id/board_serial:123490EN400015
/sys/class/dmi/id/board_vendor:SAMSUNG ELECTRONICS CO., LTD.
/sys/class/dmi/id/board_version:Not Applicable
/sys/class/dmi/id/chassis_asset_tag:No Asset Tag
/sys/class/dmi/id/chassis_serial:None
/sys/class/dmi/id/chassis_type:10
/sys/class/dmi/id/chassis_vendor:SAMSUNG ELECTRONICS CO., LTD.
/sys/class/dmi/id/chassis_version:N/A
/sys/class/dmi/id/modalias:dmi:bvnPhoenixTechnologiesLtd.:bvr07LI.MP00.20080926.SCY:bd09/26/2008:svnSAMSUNGELECTRONICSCO.,LTD.:pnR510/P510:pvrNotApplicable:rvnSAMSUNGELECTRONICSCO.,LTD.:rnR510/P510:rvrNotApplicable:cvnSAMSUNGELECTRONICSCO.,LTD.:ct10:cvrN/A:
/sys/class/dmi/id/product_name:R510/P510
/sys/class/dmi/id/product_serial:EJ1893DQ700599
/sys/class/dmi/id/product_uuid:60B6C327-D21D-B211-8000-B5CE02D5709F
/sys/class/dmi/id/product_version:Not Applicable
/sys/class/dmi/id/sys_vendor:SAMSUNG ELECTRONICS CO., LTD.
/sys/class/dmi/id/uevent:MODALIAS=dmi:bvnPhoenixTechnologiesLtd.:bvr07LI.MP00.20080926.SCY:bd09/26/2008:svnSAMSUNGELECTRONICSCO.,LTD.:pnR510/P510:pvrNotApplicable:rvnSAMSUNGELECTRONICSCO.,LTD.:rnR510/P510:rvrNotApplicable:cvnSAMSUNGELECTRONICSCO.,LTD.:ct10:cvrN/A:

This is the info you requested

I've sent it before

Is there a way to make the brightness more finer controlled using this
interface?

Regards

Mike

2009/8/26 Greg KH <greg@kroah.com>:
> On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote:
>> I can confirm that it works however it does have different behaviour
>>
>> At the lowest setting the backlight is still on and the screen even
>> though dull is still readable with the previous patch the screen was
>> black
>>
>> This is what it says in my dmesg:
>>
>> This computer supports SABI==f494d
>> SABI header:
>>  SMI Port Number = 0x00b2
>>  SMI Interface Function = 0xc0
>>  SMI enable memory buffer = 0xc1
>>  SMI restore memory buffer = 0xc2
>>  SABI data offset = 0x0f00
>>  SABI data segment = 0xdf01
>>  BIOS interface version = 0x32
>>  KBD Launcher string = 0x53
>> SABI Interface = ffff8800000dff10
>> Model Name R510
>> backlight = 0x01
>> wireless button = 0x01
>> brightness = 0x08
>> etiquette mode = 0x00
>> cpu temp = 0x45
>
> Thanks.
>
> Can you provide me the output of:
>        grep . /sys/class/dmi/id/*
>
> So I can add this laptop to the driver?
>
>> Do you have any idea why my brightness buttons aren't working correctly?
>
> See the other responses, they are working "correctly" now, a 0 setting
> will not turn off the light, as it is not supposed to.
>
> thanks,
>
> greg k-h
>
Mike Lothian Aug. 26, 2009, 9:56 p.m. UTC | #9
2009/8/26 Greg KH <greg@kroah.com>:
> On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote
>
>> Do you have any idea why my brightness buttons aren't working correctly?
>
> See the other responses, they are working "correctly" now, a 0 setting
> will not turn off the light, as it is not supposed to.

What other responses?
Mike Lothian Aug. 29, 2009, 9:16 p.m. UTC | #10
2009/8/26 Mike Lothian <mike@fireburn.co.uk>:
> 2009/8/26 Greg KH <greg@kroah.com>:
>> On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote
>>
>>> Do you have any idea why my brightness buttons aren't working correctly?
>>
>> See the other responses, they are working "correctly" now, a 0 setting
>> will not turn off the light, as it is not supposed to.
>
> What other responses?
>

I think the problem maybe due to the intel KMS driver not exporting
BACKLIGHT information via Xrandr ie xbacklight doesn't work

Also using the previous patch the one with 255 settings, once the
brightness goes low enough to switch of the backlight the backlight
refuses to come back on no matter what is value is then set

Regards

Mike
Greg KH Aug. 31, 2009, 5:51 p.m. UTC | #11
On Sat, Aug 29, 2009 at 10:16:54PM +0100, Mike Lothian wrote:
> 2009/8/26 Mike Lothian <mike@fireburn.co.uk>:
> > 2009/8/26 Greg KH <greg@kroah.com>:
> >> On Sat, Aug 22, 2009 at 10:27:58AM +0100, Mike Lothian wrote
> >>
> >>> Do you have any idea why my brightness buttons aren't working correctly?
> >>
> >> See the other responses, they are working "correctly" now, a 0 setting
> >> will not turn off the light, as it is not supposed to.
> >
> > What other responses?
> >
> 
> I think the problem maybe due to the intel KMS driver not exporting
> BACKLIGHT information via Xrandr ie xbacklight doesn't work
> 
> Also using the previous patch the one with 255 settings, once the
> brightness goes low enough to switch of the backlight the backlight
> refuses to come back on no matter what is value is then set

Yes, this is because poking the PCI config register is not the "proper"
way to do this for this hardware platform.

thanks,

greg k-h
Greg KH Aug. 31, 2009, 5:54 p.m. UTC | #12
On Wed, Aug 26, 2009 at 10:53:46PM +0100, Mike Lothian wrote:
> tau / # grep . /sys/class/dmi/id/*
> /sys/class/dmi/id/bios_date:09/26/2008
> /sys/class/dmi/id/bios_vendor:Phoenix Technologies Ltd.
> /sys/class/dmi/id/bios_version:07LI.MP00.20080926.SCY
> /sys/class/dmi/id/board_name:R510/P510
> /sys/class/dmi/id/board_serial:123490EN400015
> /sys/class/dmi/id/board_vendor:SAMSUNG ELECTRONICS CO., LTD.
> /sys/class/dmi/id/board_version:Not Applicable
> /sys/class/dmi/id/chassis_asset_tag:No Asset Tag
> /sys/class/dmi/id/chassis_serial:None
> /sys/class/dmi/id/chassis_type:10
> /sys/class/dmi/id/chassis_vendor:SAMSUNG ELECTRONICS CO., LTD.
> /sys/class/dmi/id/chassis_version:N/A
> /sys/class/dmi/id/modalias:dmi:bvnPhoenixTechnologiesLtd.:bvr07LI.MP00.20080926.SCY:bd09/26/2008:svnSAMSUNGELECTRONICSCO.,LTD.:pnR510/P510:pvrNotApplicable:rvnSAMSUNGELECTRONICSCO.,LTD.:rnR510/P510:rvrNotApplicable:cvnSAMSUNGELECTRONICSCO.,LTD.:ct10:cvrN/A:
> /sys/class/dmi/id/product_name:R510/P510
> /sys/class/dmi/id/product_serial:EJ1893DQ700599
> /sys/class/dmi/id/product_uuid:60B6C327-D21D-B211-8000-B5CE02D5709F
> /sys/class/dmi/id/product_version:Not Applicable
> /sys/class/dmi/id/sys_vendor:SAMSUNG ELECTRONICS CO., LTD.
> /sys/class/dmi/id/uevent:MODALIAS=dmi:bvnPhoenixTechnologiesLtd.:bvr07LI.MP00.20080926.SCY:bd09/26/2008:svnSAMSUNGELECTRONICSCO.,LTD.:pnR510/P510:pvrNotApplicable:rvnSAMSUNGELECTRONICSCO.,LTD.:rnR510/P510:rvrNotApplicable:cvnSAMSUNGELECTRONICSCO.,LTD.:ct10:cvrN/A:
> 
> This is the info you requested

Thanks, I've added it to the driver now.

> I've sent it before

Ick, sorry, I must have missed that.

> Is there a way to make the brightness more finer controlled using this
> interface?

Not that I know of, sorry, it looks like the hardware is only allowing
this to be changed in 8 levels of increments.  Windows should work this
same way, right?

thanks,

greg k-h
Matthias Hopf Sept. 8, 2009, 4:05 p.m. UTC | #13
On Aug 31, 09 10:54:29 -0700, Greg KH wrote:
> > Is there a way to make the brightness more finer controlled using this
> > interface?
> Not that I know of, sorry, it looks like the hardware is only allowing
> this to be changed in 8 levels of increments.  Windows should work this
> same way, right?

AFAIR at one point you deliberately decided to expose only 8 levels to
the user - while the hardware was capable of more levels (I think it was
something about some user land tool expecting something like that).
But I may be utterly wrong.

I'd love to have more fine grained control on my NC10 as well.

Thanks for the work so far

Matthias
Matthias Hopf Sept. 8, 2009, 4:06 p.m. UTC | #14
On Aug 29, 09 22:16:54 +0100, Mike Lothian wrote:
> I think the problem maybe due to the intel KMS driver not exporting
> BACKLIGHT information via Xrandr ie xbacklight doesn't work

That is fixed in git master. It's also called "Backlight" now (according
to RandR style guides), with BACKLIGHT as a fallback.

Matthias
Greg KH Sept. 8, 2009, 4:31 p.m. UTC | #15
On Tue, Sep 08, 2009 at 06:05:03PM +0200, Matthias Hopf wrote:
> On Aug 31, 09 10:54:29 -0700, Greg KH wrote:
> > > Is there a way to make the brightness more finer controlled using this
> > > interface?
> > Not that I know of, sorry, it looks like the hardware is only allowing
> > this to be changed in 8 levels of increments.  Windows should work this
> > same way, right?
> 
> AFAIR at one point you deliberately decided to expose only 8 levels to
> the user - while the hardware was capable of more levels (I think it was
> something about some user land tool expecting something like that).
> But I may be utterly wrong.

Yes, that was when we were blindly poking in the PCI config space.  We
aren't supposed to be doing that :)

> I'd love to have more fine grained control on my NC10 as well.

I have reports that Windows also only supports 8 levels, so that is what
this hardware controller is possible of supporting, sorry.

thanks,

greg k-h
Mike Lothian Sept. 8, 2009, 9:51 p.m. UTC | #16
2009/9/8 Matthias Hopf <mhopf@suse.de>:
> On Aug 29, 09 22:16:54 +0100, Mike Lothian wrote:
>> I think the problem maybe due to the intel KMS driver not exporting
>> BACKLIGHT information via Xrandr ie xbacklight doesn't work
>
> That is fixed in git master. It's also called "Backlight" now (according
> to RandR style guides), with BACKLIGHT as a fallback.
>

I'm using the latest git however the backlight property isn't exported
in either case. I'm using a GM45 if this is significant

Cheers

Mike
Matthias Hopf Sept. 9, 2009, 11:04 a.m. UTC | #17
On Sep 08, 09 09:31:24 -0700, Greg KH wrote:
> > AFAIR at one point you deliberately decided to expose only 8 levels to
> > the user - while the hardware was capable of more levels (I think it was
> > something about some user land tool expecting something like that).
> > But I may be utterly wrong.
> 
> Yes, that was when we were blindly poking in the PCI config space.  We
> aren't supposed to be doing that :)
> 
> > I'd love to have more fine grained control on my NC10 as well.
> 
> I have reports that Windows also only supports 8 levels, so that is what
> this hardware controller is possible of supporting, sorry.

Hm. This is contradicting. On the one hand the hardware is capable of
more levels. On the other it isn't *reporting* more levels.

I wouldn't rely on anything what Windows supports. There can be
arbitrary limitations in the driver.

Matthias
Greg KH Sept. 9, 2009, 1:25 p.m. UTC | #18
On Wed, Sep 09, 2009 at 01:04:15PM +0200, Matthias Hopf wrote:
> On Sep 08, 09 09:31:24 -0700, Greg KH wrote:
> > > AFAIR at one point you deliberately decided to expose only 8 levels to
> > > the user - while the hardware was capable of more levels (I think it was
> > > something about some user land tool expecting something like that).
> > > But I may be utterly wrong.
> > 
> > Yes, that was when we were blindly poking in the PCI config space.  We
> > aren't supposed to be doing that :)
> > 
> > > I'd love to have more fine grained control on my NC10 as well.
> > 
> > I have reports that Windows also only supports 8 levels, so that is what
> > this hardware controller is possible of supporting, sorry.
> 
> Hm. This is contradicting. On the one hand the hardware is capable of
> more levels. On the other it isn't *reporting* more levels.
> 
> I wouldn't rely on anything what Windows supports. There can be
> arbitrary limitations in the driver.

The hardware seems to allow multiple levels if you poke a raw value into
a random PCI config location.

However, this is not the correct way to control this hardware, a SMI
call needs to be made instead.  And for that call, only 8 levels are
available.

So it's not a limitation in the Windows driver, it is a limitation in
the BIOS provided by the manufacturer.

thanks,

greg k-h
Mike Lothian Sept. 11, 2009, 2:12 a.m. UTC | #19
2009/9/9 Matthias Hopf <mhopf@suse.de>:
> On Sep 08, 09 22:51:11 +0100, Mike Lothian wrote:
>> 2009/9/8 Matthias Hopf <mhopf@suse.de>:
>> > On Aug 29, 09 22:16:54 +0100, Mike Lothian wrote:
>> >> I think the problem maybe due to the intel KMS driver not exporting
>> >> BACKLIGHT information via Xrandr ie xbacklight doesn't work
>> >
>> > That is fixed in git master. It's also called "Backlight" now (according
>> > to RandR style guides), with BACKLIGHT as a fallback.
>>
>> I'm using the latest git however the backlight property isn't exported
>> in either case. I'm using a GM45 if this is significant
>
> Can you check whether you have any backlight controlling module loaded?
> Just check whether you have anything in /sys/class/backlight.
>
> If there isn't, try loading the samsung module with force=1, and if that
> works ask GregKH to add your pci ids to the driver.
>
> Matthias
>
> --
> Matthias Hopf <mhopf@suse.de>      __        __   __
> Maxfeldstr. 5 / 90409 Nuernberg   (_   | |  (_   |__          mat@mshopf.de
> Phone +49-911-74053-715           __)  |_|  __)  |__  R & D   www.mshopf.de
>

Hi

Yes I have:

/sys/class/backlight/samsung/*

Regards

Mike
Mike Lothian Sept. 11, 2009, 2:14 a.m. UTC | #20
2009/9/9 Greg KH <greg@kroah.com>:
> On Wed, Sep 09, 2009 at 01:04:15PM +0200, Matthias Hopf wrote:
>> On Sep 08, 09 09:31:24 -0700, Greg KH wrote:
>> > > AFAIR at one point you deliberately decided to expose only 8 levels to
>> > > the user - while the hardware was capable of more levels (I think it was
>> > > something about some user land tool expecting something like that).
>> > > But I may be utterly wrong.
>> >
>> > Yes, that was when we were blindly poking in the PCI config space.  We
>> > aren't supposed to be doing that :)
>> >
>> > > I'd love to have more fine grained control on my NC10 as well.
>> >
>> > I have reports that Windows also only supports 8 levels, so that is what
>> > this hardware controller is possible of supporting, sorry.
>>
>> Hm. This is contradicting. On the one hand the hardware is capable of
>> more levels. On the other it isn't *reporting* more levels.
>>
>> I wouldn't rely on anything what Windows supports. There can be
>> arbitrary limitations in the driver.
>
> The hardware seems to allow multiple levels if you poke a raw value into
> a random PCI config location.
>
> However, this is not the correct way to control this hardware, a SMI
> call needs to be made instead.  And for that call, only 8 levels are
> available.
>
> So it's not a limitation in the Windows driver, it is a limitation in
> the BIOS provided by the manufacturer.
>
> thanks,
>
> greg k-h
>

Who's the best person to talk to about getting my laptop added to

drivers/input/keyboard/atkbd.c

To stop the stuck keys

Cheers

Mike
Greg KH Sept. 11, 2009, 2:50 a.m. UTC | #21
On Fri, Sep 11, 2009 at 03:14:46AM +0100, Mike Lothian wrote:
> 2009/9/9 Greg KH <greg@kroah.com>:
> > On Wed, Sep 09, 2009 at 01:04:15PM +0200, Matthias Hopf wrote:
> >> On Sep 08, 09 09:31:24 -0700, Greg KH wrote:
> >> > > AFAIR at one point you deliberately decided to expose only 8 levels to
> >> > > the user - while the hardware was capable of more levels (I think it was
> >> > > something about some user land tool expecting something like that).
> >> > > But I may be utterly wrong.
> >> >
> >> > Yes, that was when we were blindly poking in the PCI config space.  We
> >> > aren't supposed to be doing that :)
> >> >
> >> > > I'd love to have more fine grained control on my NC10 as well.
> >> >
> >> > I have reports that Windows also only supports 8 levels, so that is what
> >> > this hardware controller is possible of supporting, sorry.
> >>
> >> Hm. This is contradicting. On the one hand the hardware is capable of
> >> more levels. On the other it isn't *reporting* more levels.
> >>
> >> I wouldn't rely on anything what Windows supports. There can be
> >> arbitrary limitations in the driver.
> >
> > The hardware seems to allow multiple levels if you poke a raw value into
> > a random PCI config location.
> >
> > However, this is not the correct way to control this hardware, a SMI
> > call needs to be made instead.  And for that call, only 8 levels are
> > available.
> >
> > So it's not a limitation in the Windows driver, it is a limitation in
> > the BIOS provided by the manufacturer.
> >
> > thanks,
> >
> > greg k-h
> >
> 
> Who's the best person to talk to about getting my laptop added to
> 
> drivers/input/keyboard/atkbd.c
> 
> To stop the stuck keys

The input maintainer and developers as found in the kernel MAINTAINERS
file?
Mike Lothian Sept. 13, 2009, 4:28 p.m. UTC | #22
2009/9/11 Mike Lothian <mike@fireburn.co.uk>:
> 2009/9/11 Matthias Hopf <mhopf@suse.de>:
>> On Sep 11, 09 03:12:27 +0100, Mike Lothian wrote:
>>> > Can you check whether you have any backlight controlling module loaded?
>>> > Just check whether you have anything in /sys/class/backlight.
>>> Yes I have:
>>> /sys/class/backlight/samsung/*
>>efbcf29dd1a1ca058b7a2a93f0685102c06c9369
>
>> And your X driver still doesn't export the Backlight property? That's
>> weird...
>> Please check the git version number of the X driver.
>>
>> Matthias
>>
>> --
>> Matthias Hopf <mhopf@suse.de>      __        __   __
>> Maxfeldstr. 5 / 90409 Nuernberg   (_   | |  (_   |__          mat@mshopf.de
>> Phone +49-911-74053-715           __)  |_|  __)  |__  R & D   www.mshopf.de
>>
>
> I'm at git commit efbcf29dd1a1ca058b7a2a93f0685102c06c9369 of the master branch
>
> II) Loading /usr/lib64/xorg/modules/drivers/intel_drv.so
> (II) Module intel: vendor="X.Org Foundation"
>        compiled for 1.6.99.900, module version = 2.8.99
>        Module class: X.Org Video Driver
>        ABI class: X.Org Video Driver, version 6.0
> (II) intel: Driver for Intel Integrated Graphics Chipsets: i810,
>        i810-dc100, i810e, i815, i830M, 845G, 852GM/855GM, 865G, 915G,
>        E7221 (i915), 915GM, 945G, 945GM, 945GME, Pineview GM, Pineview G,
>        965G, G35, 965Q, 946GZ, 965GM, 965GME/GLE, G33, Q35, Q33, GM45,
>        4 Series, G45/G43, Q45/Q43, G41, B43, Clarkdale, Arrandale
>
Matthias Hopf Sept. 22, 2009, 4:02 p.m. UTC | #23
On Sep 13, 09 17:28:27 +0100, Mike Lothian wrote:
> >>> > Can you check whether you have any backlight controlling module loaded?
> >>> > Just check whether you have anything in /sys/class/backlight.
> >>> Yes I have:
> >>> /sys/class/backlight/samsung/*
> >
> >> And your X driver still doesn't export the Backlight property? That's
> >> weird...
> >> Please check the git version number of the X driver.
> >
> > I'm at git commit efbcf29dd1a1ca058b7a2a93f0685102c06c9369 of the master branch

Sorry for the late answer.

It turned out that I didn't commit the name of the samsung backlight
driver to the intel video driver (*blush*) - mainly because GregKH and I
weren't on the same level what the final name of the driver will be (I
supposed something more general, as it probably won't be samsung
centric).

Now that the name presumably stays fixed as "samsung" I finally
committed the name to the intel driver. It's commit #7e7db7a.

CU

Matthias
Mike Lothian Sept. 24, 2009, 5:59 a.m. UTC | #24
2009/9/22 Matthias Hopf <mhopf@suse.de>:
> On Sep 13, 09 17:28:27 +0100, Mike Lothian wrote:
>> >>> > Can you check whether you have any backlight controlling module loaded?
>> >>> > Just check whether you have anything in /sys/class/backlight.
>> >>> Yes I have:
>> >>> /sys/class/backlight/samsung/*
>> >
>> >> And your X driver still doesn't export the Backlight property? That's
>> >> weird...
>> >> Please check the git version number of the X driver.
>> >
>> > I'm at git commit efbcf29dd1a1ca058b7a2a93f0685102c06c9369 of the master branch
>
> Sorry for the late answer.
>
> It turned out that I didn't commit the name of the samsung backlight
> driver to the intel video driver (*blush*) - mainly because GregKH and I
> weren't on the same level what the final name of the driver will be (I
> supposed something more general, as it probably won't be samsung
> centric).
>
> Now that the name presumably stays fixed as "samsung" I finally
> committed the name to the intel driver. It's commit #7e7db7a.
>
> CU
>
> Matthias
>
> --
> Matthias Hopf <mhopf@suse.de>      __        __   __
> Maxfeldstr. 5 / 90409 Nuernberg   (_   | |  (_   |__          mat@mshopf.de
> Phone +49-911-74053-715           __)  |_|  __)  |__  R & D   www.mshopf.de
>

Yip I think that's fixed it thanks

Looks like this laptop is now 100% linux compatible (apart from having
to force it to use only 3 gigs of ram with MEM=4g boot parm)

Cheers

Mike
diff mbox

Patch

--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -425,4 +425,16 @@  config ACPI_TOSHIBA
 
 	  If you have a legacy free Toshiba laptop (such as the Libretto L1
 	  series), say Y.
+
+config SAMSUNG_LAPTOP
+	tristate "Samsung Laptop driver"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on DMI
+	---help---
+	  This driver adds support to control the backlight on a number of
+	  Samsung laptops, like the N130, and control for some of the LEDs
+
+	  It will only be loaded on laptops that properly need it, so it is
+	  safe to say Y here.
+
 endif # X86_PLATFORM_DEVICES
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -20,3 +20,4 @@  obj-$(CONFIG_INTEL_MENLOW)	+= intel_menl
 obj-$(CONFIG_ACPI_WMI)		+= wmi.o
 obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
+obj-$(CONFIG_SAMSUNG_LAPTOP)	+= samsung-laptop.o
--- /dev/null
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -0,0 +1,420 @@ 
+/*
+ * Samsung N130 and NC120 Laptop driver
+ *
+ * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de)
+ * Copyright (C) 2009 Novell Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/dmi.h>
+#include <linux/platform_device.h>
+
+/*
+ * This driver is needed because a number of Samsung laptops do not hook
+ * their control settings through ACPI.  So we have to poke around in the
+ * BIOS to do things like brightness values, and "special" key controls.
+ */
+
+
+/*
+ * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
+ * be reserved by the BIOS (which really doesn't make much sense), we tell
+ * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
+ */
+#define MAX_BRIGHT	0x07
+
+/* get model returns 4 characters that describe the model of the laptop */
+#define SABI_GET_MODEL			0x04
+
+/* Brightness is 0 - 8, as described above.  Value 0 is for the BIOS to use */
+#define SABI_GET_BRIGHTNESS		0x10
+#define SABI_SET_BRIGHTNESS		0x11
+
+/* 0 is off, 1 is on, and 2 is a second user-defined key? */
+#define SABI_GET_WIRELESS_BUTTON	0x12
+#define SABI_SET_WIRELESS_BUTTON	0x13
+
+/* Temperature is returned in degress Celsius from what I can guess. */
+#define SABI_GET_CPU_TEMP		0x29
+
+/* 0 is off, 1 is on.  Doesn't seem to work on a N130 for some reason */
+#define SABI_GET_BACKLIGHT		0x2d
+#define SABI_SET_BACKLIGHT		0x2e
+
+/*
+ * This is different
+ * There is 3 different modes here:
+ *   0 - off
+ *   1 - on
+ *   2 - max performance mode
+ * off is "normal" mode.
+ * on means that whatever the bios setting for etiquette mode, is enabled.  It
+ * seems that the BIOS can set either "auto" mode, or "slow" mode.  If "slow"
+ * mode is set, the fan turns off, and the cpu is throttled down to not cause
+ * the fan to turn on if at all possible.
+ * max performance means that the processor can be overclocked and run faster
+ * then is physically possible.  Ok, maybe not physically possible, but it is
+ * overclocked.  Funny that the system has a setting for this...
+ */
+#define SABI_GET_ETIQUETTE_MODE		0x31
+#define SABI_SET_ETIQUETTE_MODE		0x32
+
+/*
+ * I imagine that on some laptops there is a bluetooth switch, but I don't know
+ * what that looks like, or where it is in the BIOS address space
+ */
+
+
+/*
+ * SABI HEADER in low memory (f0000)
+ * We need to poke through memory to find a signature in order to find the
+ * exact location of this structure.
+ */
+struct sabi_header {
+	u16 portNo;
+	u8 ifaceFunc;
+	u8 enMem;
+	u8 reMem;
+	u16 dataOffset;
+	u16 dataSegment;
+	u8 BIOSifver;
+	u8 LauncherString;
+} __attribute__((packed));
+
+/*
+ * The SABI interface that we use to write and read values from the system.
+ * It is found by looking at the dataOffset and dataSegment values in the sabi
+ * header structure
+ */
+struct sabi_interface {
+	u16 mainfunc;
+	u16 subfunc;
+	u8 complete;
+	u8 retval[20];
+} __attribute__((packed));
+
+/* Structure to get data back to the calling function */
+struct sabi_retval {
+	u8 retval[20];
+};
+
+static struct sabi_header __iomem *sabi;
+static struct sabi_interface __iomem *sabi_iface;
+static void __iomem *f0000_segment;
+static struct backlight_device *backlight_device;
+static struct mutex sabi_mutex;
+static struct platform_device *sdev;
+
+static int force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Disable the DMI check and forces the driver to be loaded");
+
+static int debug;
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+static int sabi_get_command(u8 command, struct sabi_retval *sretval)
+{
+	int retval = 0;
+
+	mutex_lock(&sabi_mutex);
+
+	/* enable memory to be able to write to it */
+	outb(readb(&sabi->enMem), readw(&sabi->portNo));
+
+	/* write out the command */
+	writew(0x5843, &sabi_iface->mainfunc);
+	writew(command, &sabi_iface->subfunc);
+	writeb(0, &sabi_iface->complete);
+	outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
+
+	/* sleep for a bit to let the command complete */
+	msleep(100);
+
+	/* write protect memory to make it safe */
+	outb(readb(&sabi->reMem), readw(&sabi->portNo));
+
+	/* see if the command actually succeeded */
+	if (readb(&sabi_iface->complete) == 0xaa &&
+	    readb(&sabi_iface->retval[0]) != 0xff) {
+		/*
+		 * It did!
+		 * Save off the data into a structure so the caller use it.
+		 * Right now we only care about the first 4 bytes,
+		 * I suppose there are commands that need more, but I don't
+		 * know about them.
+		 */
+		sretval->retval[0] = readb(&sabi_iface->retval[0]);
+		sretval->retval[1] = readb(&sabi_iface->retval[1]);
+		sretval->retval[2] = readb(&sabi_iface->retval[2]);
+		sretval->retval[3] = readb(&sabi_iface->retval[3]);
+		goto exit;
+	}
+
+	/* Something bad happened, so report it and error out */
+	printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
+		command, readb(&sabi_iface->complete),
+		readb(&sabi_iface->retval[0]));
+	retval = -EINVAL;
+exit:
+	mutex_unlock(&sabi_mutex);
+	return retval;
+
+}
+
+static int sabi_set_command(u8 command, u8 data)
+{
+	int retval = 0;
+
+	mutex_lock(&sabi_mutex);
+
+	/* enable memory to be able to write to it */
+	outb(readb(&sabi->enMem), readw(&sabi->portNo));
+
+	/* write out the command */
+	writew(0x5843, &sabi_iface->mainfunc);
+	writew(command, &sabi_iface->subfunc);
+	writeb(0, &sabi_iface->complete);
+	writeb(data, &sabi_iface->retval[0]);
+	outb(readb(&sabi->ifaceFunc), readw(&sabi->portNo));
+
+	/* sleep for a bit to let the command complete */
+	msleep(100);
+
+	/* write protect memory to make it safe */
+	outb(readb(&sabi->reMem), readw(&sabi->portNo));
+
+	/* see if the command actually succeeded */
+	if (readb(&sabi_iface->complete) == 0xaa &&
+	    readb(&sabi_iface->retval[0]) != 0xff) {
+		/* it did! */
+		goto exit;
+	}
+
+	/* Something bad happened, so report it and error out */
+	printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n",
+		command, readb(&sabi_iface->complete),
+		readb(&sabi_iface->retval[0]));
+	retval = -EINVAL;
+exit:
+	mutex_unlock(&sabi_mutex);
+	return retval;
+}
+
+static u8 read_brightness(void)
+{
+	struct sabi_retval sretval;
+	int user_brightness = 0;
+	int retval;
+
+	retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
+	if (!retval)
+		user_brightness = sretval.retval[0];
+		if (user_brightness != 0)
+			--user_brightness;
+	return user_brightness;
+}
+
+static void set_brightness(u8 user_brightness)
+{
+	sabi_set_command(SABI_SET_BRIGHTNESS, user_brightness + 1);
+}
+
+static int get_brightness(struct backlight_device *bd)
+{
+	return bd->props.brightness;
+}
+
+static int update_status(struct backlight_device *bd)
+{
+	set_brightness(bd->props.brightness);
+	return 0;
+}
+
+static struct backlight_ops backlight_ops = {
+	.get_brightness	= get_brightness,
+	.update_status	= update_status,
+};
+
+static int __init dmi_check_cb(const struct dmi_system_id *id)
+{
+	printk(KERN_INFO KBUILD_MODNAME ": found laptop model '%s'\n",
+		id->ident);
+	return 0;
+}
+
+static struct dmi_system_id __initdata samsung_dmi_table[] = {
+	{
+		.ident = "N120",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "N120"),
+			DMI_MATCH(DMI_BOARD_NAME, "N120"),
+		},
+		.callback = dmi_check_cb,
+	},
+	{
+		.ident = "N130",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
+			DMI_MATCH(DMI_BOARD_NAME, "N130"),
+		},
+		.callback = dmi_check_cb,
+	},
+	{ },
+};
+
+
+static int __init samsung_init(void)
+{
+	struct sabi_retval sretval;
+	const char *testStr = "SwSmi@";
+	void __iomem *memcheck;
+	unsigned int ifaceP;
+	int pStr;
+	int loca;
+	int retval;
+
+	mutex_init(&sabi_mutex);
+
+	if (!force && !dmi_check_system(samsung_dmi_table))
+		return -ENODEV;
+
+	f0000_segment = ioremap(0xf0000, 0xffff);
+	if (!f0000_segment) {
+		printk(KERN_ERR "Can't map the segment at 0xf0000\n");
+		return -EINVAL;
+	}
+
+	/* Try to find the signature "SwSmi@" in memory to find the header */
+	pStr = 0;
+	memcheck = f0000_segment;
+	for (loca = 0; loca < 0xffff; loca++) {
+		char temp = readb(memcheck + loca);
+
+		if (temp == testStr[pStr]) {
+			if (pStr == 5)
+				break;
+			++pStr;
+		} else {
+			pStr = 0;
+		}
+	}
+	if (loca == 0xffff) {
+		printk(KERN_INFO "This computer does not support SABI\n");
+		goto error_no_signature;
+		}
+
+	/* point to the SMI port Number */
+	loca += 1;
+	sabi = (struct sabi_header __iomem *)(loca + memcheck);
+	if (!sabi) {
+		printk(KERN_ERR "Can't remap %p\n", loca + memcheck);
+		goto exit;
+	}
+
+	printk(KERN_INFO "This computer supports SABI==%x\n", loca + 0xf0000 - 6);
+	printk(KERN_INFO "SABI header:\n");
+	printk(KERN_INFO " SMI Port Number = 0x%04x\n", readw(&sabi->portNo));
+	printk(KERN_INFO " SMI Interface Function = 0x%02x\n", readb(&sabi->ifaceFunc));
+	printk(KERN_INFO " SMI enable memory buffer = 0x%02x\n", readb(&sabi->enMem));
+	printk(KERN_INFO " SMI restore memory buffer = 0x%02x\n", readb(&sabi->reMem));
+	printk(KERN_INFO " SABI data offset = 0x%04x\n", readw(&sabi->dataOffset));
+	printk(KERN_INFO " SABI data segment = 0x%04x\n", readw(&sabi->dataSegment));
+	printk(KERN_INFO " BIOS interface version = 0x%02x\n", readb(&sabi->BIOSifver));
+	printk(KERN_INFO " KBD Launcher string = 0x%02x\n", readb(&sabi->LauncherString));
+
+	/* Get a pointer to the SABI Interface */
+	ifaceP = (readw(&sabi->dataSegment) & 0x0ffff) << 4;
+	ifaceP += readw(&sabi->dataOffset) & 0x0ffff;
+	sabi_iface = (struct sabi_interface __iomem *)ioremap(ifaceP, 16);
+	if (!sabi_iface) {
+		printk(KERN_ERR "Can't remap %x\n", ifaceP);
+		goto exit;
+	}
+	printk(KERN_INFO "SABI Interface = %p\n", sabi_iface);
+
+	retval = sabi_get_command(SABI_GET_MODEL, &sretval);
+	if (!retval) {
+		printk(KERN_INFO "Model Name %c%c%c%c\n",
+			sretval.retval[0],
+			sretval.retval[1],
+			sretval.retval[2],
+			sretval.retval[3]);
+	}
+
+	retval = sabi_get_command(SABI_GET_BACKLIGHT, &sretval);
+	if (!retval)
+		printk("backlight = 0x%02x\n", sretval.retval[0]);
+
+	retval = sabi_get_command(SABI_GET_WIRELESS_BUTTON, &sretval);
+	if (!retval)
+		printk("wireless button = 0x%02x\n", sretval.retval[0]);
+
+	retval = sabi_get_command(SABI_GET_BRIGHTNESS, &sretval);
+	if (!retval)
+		printk("brightness = 0x%02x\n", sretval.retval[0]);
+
+	retval = sabi_get_command(SABI_GET_ETIQUETTE_MODE, &sretval);
+	if (!retval)
+		printk("etiquette mode = 0x%02x\n", sretval.retval[0]);
+	retval = sabi_get_command(SABI_GET_CPU_TEMP, &sretval);
+	if (!retval)
+		printk("cpu temp = 0x%02x\n", sretval.retval[0]);
+
+	/* knock up a platform device to hang stuff off of */
+	sdev = platform_device_register_simple("samsung", -1, NULL, 0);
+	if (IS_ERR(sdev))
+		goto error_no_platform;
+
+	/* create a backlight device to talk to this one */
+	backlight_device = backlight_device_register("samsung", &sdev->dev,
+						     NULL, &backlight_ops);
+	if (IS_ERR(backlight_device))
+		goto error_no_backlight;
+
+	backlight_device->props.max_brightness = MAX_BRIGHT;
+	backlight_device->props.brightness = read_brightness();
+	backlight_device->props.power = FB_BLANK_UNBLANK;
+	backlight_update_status(backlight_device);
+
+exit:
+	return 0;
+
+error_no_backlight:
+	platform_device_unregister(sdev);
+
+error_no_platform:
+	iounmap(sabi_iface);
+
+error_no_signature:
+	iounmap(f0000_segment);
+	return -EINVAL;
+}
+
+static void __exit samsung_exit(void)
+{
+	backlight_device_unregister(backlight_device);
+	iounmap(sabi_iface);
+	iounmap(f0000_segment);
+	platform_device_unregister(sdev);
+}
+
+module_init(samsung_init);
+module_exit(samsung_exit);
+
+MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
+MODULE_DESCRIPTION("Samsung Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN120:*:rnN120:*");
+MODULE_ALIAS("dmi:*:svnSAMSUNGELECTRONICSCO.,LTD.:pnN130:*:rnN130:*");