[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