[Intel-gfx] New Samsung driver for backlight control.

Mike Lothian mike at fireburn.co.uk
Sat Aug 22 11:41:15 CEST 2009


2009/8/22 Greg KH <greg at 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 at 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 at gmail.com> for cleanups
> and other suggestions on how to make the driver simpler.
>
> Cc: Soeren Sonnenburg <bugreports at nn7.de>
> Cc: Jérémie Huchet <jeremie at lamah.info>
> Cc: Dmitry Torokhov <dmitry.torokhov at gmail.com>
> Signed-off-by: Greg Kroah-Hartman <gregkh at 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 at 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 at 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



More information about the Intel-gfx mailing list