lkml.org 
[lkml]   [2011]   [Feb]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[PATCH] Platform: add Samsung Laptop platform driver
    From: Greg Kroah-Hartman <gregkh@suse.de>

    This adds the samsung-laptop driver to the kernel. It now supports
    all known Samsung laptops.

    Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
    ---
    Documentation/ABI/testing/sysfs-driver-samsung-laptop | 19
    drivers/platform/x86/Kconfig | 13
    drivers/platform/x86/Makefile | 1
    drivers/platform/x86/samsung-laptop.c | 768 ++++++++++++++++++
    4 files changed, 801 insertions(+)

    Matthew, this is a copy of the latest version in the linux-next tree of the
    driver. Once it has been accepted into your tree, I'll delete the
    staging driver. Or, if you want to give your ACK, I can just move the
    driver from staging into this directory in my tree, which will properly
    preserve the history of the code.


    diff --git a/Documentation/ABI/testing/sysfs-driver-samsung-laptop b/Documentation/ABI/testing/sysfs-driver-samsung-laptop
    new file mode 100644
    index 0000000..0a81023
    --- /dev/null
    +++ b/Documentation/ABI/testing/sysfs-driver-samsung-laptop
    @@ -0,0 +1,19 @@
    +What: /sys/devices/platform/samsung/performance_level
    +Date: January 1, 2010
    +KernelVersion: 2.6.33
    +Contact: Greg Kroah-Hartman <gregkh@suse.de>
    +Description: Some Samsung laptops have different "performance levels"
    + that are can be modified by a function key, and by this
    + sysfs file. These values don't always make a whole lot
    + of sense, but some users like to modify them to keep
    + their fans quiet at all costs. Reading from this file
    + will show the current performance level. Writing to the
    + file can change this value.
    + Valid options:
    + "silent"
    + "normal"
    + "overclock"
    + Note that not all laptops support all of these options.
    + Specifically, not all support the "overclock" option,
    + and it's still unknown if this value even changes
    + anything, other than making the user feel a bit better.
    diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
    index d163bc2..da9aed1 100644
    --- a/drivers/platform/x86/Kconfig
    +++ b/drivers/platform/x86/Kconfig
    @@ -654,4 +654,17 @@ config XO1_RFKILL
    Support for enabling/disabling the WLAN interface on the OLPC XO-1
    laptop.

    +config SAMSUNG_LAPTOP
    + tristate "Samsung Laptop driver"
    + depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86
    + ---help---
    + This module implements a driver for a wide range of different
    + Samsung laptops. It offers control over the different
    + function keys, wireless LED, LCD backlight level, and
    + sometimes provides a "performance_control" sysfs file to allow
    + the performance level of the laptop to be changed.
    +
    + To compile this driver as a module, choose M here: the module
    + will be called samsung-laptop.
    +
    endif # X86_PLATFORM_DEVICES
    diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
    index 4ec4ff8..ad2fb97 100644
    --- a/drivers/platform/x86/Makefile
    +++ b/drivers/platform/x86/Makefile
    @@ -34,3 +34,4 @@ obj-$(CONFIG_INTEL_IPS) += intel_ips.o
    obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o
    obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
    obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
    +obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o
    diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
    new file mode 100644
    index 0000000..51ec621
    --- /dev/null
    +++ b/drivers/platform/x86/samsung-laptop.c
    @@ -0,0 +1,768 @@
    +/*
    + * Samsung 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/delay.h>
    +#include <linux/pci.h>
    +#include <linux/backlight.h>
    +#include <linux/fb.h>
    +#include <linux/dmi.h>
    +#include <linux/platform_device.h>
    +#include <linux/rfkill.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
    +
    +
    +#define SABI_IFACE_MAIN 0x00
    +#define SABI_IFACE_SUB 0x02
    +#define SABI_IFACE_COMPLETE 0x04
    +#define SABI_IFACE_DATA 0x05
    +
    +/* Structure to get data back to the calling function */
    +struct sabi_retval {
    + u8 retval[20];
    +};
    +
    +struct sabi_header_offsets {
    + u8 port;
    + u8 re_mem;
    + u8 iface_func;
    + u8 en_mem;
    + u8 data_offset;
    + u8 data_segment;
    +};
    +
    +struct sabi_commands {
    + /*
    + * Brightness is 0 - 8, as described above.
    + * Value 0 is for the BIOS to use
    + */
    + u8 get_brightness;
    + u8 set_brightness;
    +
    + /*
    + * first byte:
    + * 0x00 - wireless is off
    + * 0x01 - wireless is on
    + * second byte:
    + * 0x02 - 3G is off
    + * 0x03 - 3G is on
    + * TODO, verify 3G is correct, that doesn't seem right...
    + */
    + u8 get_wireless_button;
    + u8 set_wireless_button;
    +
    + /* 0 is off, 1 is on */
    + u8 get_backlight;
    + u8 set_backlight;
    +
    + /*
    + * 0x80 or 0x00 - no action
    + * 0x81 - recovery key pressed
    + */
    + u8 get_recovery_mode;
    + u8 set_recovery_mode;
    +
    + /*
    + * on seclinux: 0 is low, 1 is high,
    + * on swsmi: 0 is normal, 1 is silent, 2 is turbo
    + */
    + u8 get_performance_level;
    + u8 set_performance_level;
    +
    + /*
    + * Tell the BIOS that Linux is running on this machine.
    + * 81 is on, 80 is off
    + */
    + u8 set_linux;
    +};
    +
    +struct sabi_performance_level {
    + const char *name;
    + u8 value;
    +};
    +
    +struct sabi_config {
    + const char *test_string;
    + u16 main_function;
    + struct sabi_header_offsets header_offsets;
    + struct sabi_commands commands;
    + struct sabi_performance_level performance_levels[4];
    +};
    +
    +static struct sabi_config sabi_configs[] = {
    + {
    + .test_string = "SECLINUX",
    +
    + .main_function = 0x4c59,
    +
    + .header_offsets = {
    + .port = 0x00,
    + .re_mem = 0x02,
    + .iface_func = 0x03,
    + .en_mem = 0x04,
    + .data_offset = 0x05,
    + .data_segment = 0x07,
    + },
    +
    + .commands = {
    + .get_brightness = 0x00,
    + .set_brightness = 0x01,
    +
    + .get_wireless_button = 0x02,
    + .set_wireless_button = 0x03,
    +
    + .get_backlight = 0x04,
    + .set_backlight = 0x05,
    +
    + .get_recovery_mode = 0x06,
    + .set_recovery_mode = 0x07,
    +
    + .get_performance_level = 0x08,
    + .set_performance_level = 0x09,
    +
    + .set_linux = 0x0a,
    + },
    +
    + .performance_levels = {
    + {
    + .name = "silent",
    + .value = 0,
    + },
    + {
    + .name = "normal",
    + .value = 1,
    + },
    + { },
    + },
    + },
    + {
    + .test_string = "SwSmi@",
    +
    + .main_function = 0x5843,
    +
    + .header_offsets = {
    + .port = 0x00,
    + .re_mem = 0x04,
    + .iface_func = 0x02,
    + .en_mem = 0x03,
    + .data_offset = 0x05,
    + .data_segment = 0x07,
    + },
    +
    + .commands = {
    + .get_brightness = 0x10,
    + .set_brightness = 0x11,
    +
    + .get_wireless_button = 0x12,
    + .set_wireless_button = 0x13,
    +
    + .get_backlight = 0x2d,
    + .set_backlight = 0x2e,
    +
    + .get_recovery_mode = 0xff,
    + .set_recovery_mode = 0xff,
    +
    + .get_performance_level = 0x31,
    + .set_performance_level = 0x32,
    +
    + .set_linux = 0xff,
    + },
    +
    + .performance_levels = {
    + {
    + .name = "normal",
    + .value = 0,
    + },
    + {
    + .name = "silent",
    + .value = 1,
    + },
    + {
    + .name = "overclock",
    + .value = 2,
    + },
    + { },
    + },
    + },
    + { },
    +};
    +
    +static struct sabi_config *sabi_config;
    +
    +static void __iomem *sabi;
    +static void __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 struct rfkill *rfk;
    +
    +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;
    + u16 port = readw(sabi + sabi_config->header_offsets.port);
    +
    + mutex_lock(&sabi_mutex);
    +
    + /* enable memory to be able to write to it */
    + outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
    +
    + /* write out the command */
    + writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
    + writew(command, sabi_iface + SABI_IFACE_SUB);
    + writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
    + outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
    +
    + /* write protect memory to make it safe */
    + outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
    +
    + /* see if the command actually succeeded */
    + if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa &&
    + readb(sabi_iface + SABI_IFACE_DATA) != 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 + SABI_IFACE_DATA);
    + sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1);
    + sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2);
    + sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 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 + SABI_IFACE_COMPLETE),
    + readb(sabi_iface + SABI_IFACE_DATA));
    + retval = -EINVAL;
    +exit:
    + mutex_unlock(&sabi_mutex);
    + return retval;
    +
    +}
    +
    +static int sabi_set_command(u8 command, u8 data)
    +{
    + int retval = 0;
    + u16 port = readw(sabi + sabi_config->header_offsets.port);
    +
    + mutex_lock(&sabi_mutex);
    +
    + /* enable memory to be able to write to it */
    + outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
    +
    + /* write out the command */
    + writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
    + writew(command, sabi_iface + SABI_IFACE_SUB);
    + writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
    + writeb(data, sabi_iface + SABI_IFACE_DATA);
    + outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
    +
    + /* write protect memory to make it safe */
    + outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
    +
    + /* see if the command actually succeeded */
    + if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa &&
    + readb(sabi_iface + SABI_IFACE_DATA) != 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 + SABI_IFACE_COMPLETE),
    + readb(sabi_iface + SABI_IFACE_DATA));
    + retval = -EINVAL;
    +exit:
    + mutex_unlock(&sabi_mutex);
    + return retval;
    +}
    +
    +static void test_backlight(void)
    +{
    + struct sabi_retval sretval;
    +
    + sabi_get_command(sabi_config->commands.get_backlight, &sretval);
    + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
    +
    + sabi_set_command(sabi_config->commands.set_backlight, 0);
    + printk(KERN_DEBUG "backlight should be off\n");
    +
    + sabi_get_command(sabi_config->commands.get_backlight, &sretval);
    + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
    +
    + msleep(1000);
    +
    + sabi_set_command(sabi_config->commands.set_backlight, 1);
    + printk(KERN_DEBUG "backlight should be on\n");
    +
    + sabi_get_command(sabi_config->commands.get_backlight, &sretval);
    + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
    +}
    +
    +static void test_wireless(void)
    +{
    + struct sabi_retval sretval;
    +
    + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
    + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
    +
    + sabi_set_command(sabi_config->commands.set_wireless_button, 0);
    + printk(KERN_DEBUG "wireless led should be off\n");
    +
    + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
    + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
    +
    + msleep(1000);
    +
    + sabi_set_command(sabi_config->commands.set_wireless_button, 1);
    + printk(KERN_DEBUG "wireless led should be on\n");
    +
    + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
    + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
    +}
    +
    +static u8 read_brightness(void)
    +{
    + struct sabi_retval sretval;
    + int user_brightness = 0;
    + int retval;
    +
    + retval = sabi_get_command(sabi_config->commands.get_brightness,
    + &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_config->commands.set_brightness,
    + user_brightness + 1);
    +}
    +
    +static int get_brightness(struct backlight_device *bd)
    +{
    + return (int)read_brightness();
    +}
    +
    +static int update_status(struct backlight_device *bd)
    +{
    + set_brightness(bd->props.brightness);
    +
    + if (bd->props.power == FB_BLANK_UNBLANK)
    + sabi_set_command(sabi_config->commands.set_backlight, 1);
    + else
    + sabi_set_command(sabi_config->commands.set_backlight, 0);
    + return 0;
    +}
    +
    +static const struct backlight_ops backlight_ops = {
    + .get_brightness = get_brightness,
    + .update_status = update_status,
    +};
    +
    +static int rfkill_set(void *data, bool blocked)
    +{
    + /* Do something with blocked...*/
    + /*
    + * blocked == false is on
    + * blocked == true is off
    + */
    + if (blocked)
    + sabi_set_command(sabi_config->commands.set_wireless_button, 0);
    + else
    + sabi_set_command(sabi_config->commands.set_wireless_button, 1);
    +
    + return 0;
    +}
    +
    +static struct rfkill_ops rfkill_ops = {
    + .set_block = rfkill_set,
    +};
    +
    +static int init_wireless(struct platform_device *sdev)
    +{
    + int retval;
    +
    + rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN,
    + &rfkill_ops, NULL);
    + if (!rfk)
    + return -ENOMEM;
    +
    + retval = rfkill_register(rfk);
    + if (retval) {
    + rfkill_destroy(rfk);
    + return -ENODEV;
    + }
    +
    + return 0;
    +}
    +
    +static void destroy_wireless(void)
    +{
    + rfkill_unregister(rfk);
    + rfkill_destroy(rfk);
    +}
    +
    +static ssize_t get_performance_level(struct device *dev,
    + struct device_attribute *attr, char *buf)
    +{
    + struct sabi_retval sretval;
    + int retval;
    + int i;
    +
    + /* Read the state */
    + retval = sabi_get_command(sabi_config->commands.get_performance_level,
    + &sretval);
    + if (retval)
    + return retval;
    +
    + /* The logic is backwards, yeah, lots of fun... */
    + for (i = 0; sabi_config->performance_levels[i].name; ++i) {
    + if (sretval.retval[0] == sabi_config->performance_levels[i].value)
    + return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name);
    + }
    + return sprintf(buf, "%s\n", "unknown");
    +}
    +
    +static ssize_t set_performance_level(struct device *dev,
    + struct device_attribute *attr, const char *buf,
    + size_t count)
    +{
    + if (count >= 1) {
    + int i;
    + for (i = 0; sabi_config->performance_levels[i].name; ++i) {
    + struct sabi_performance_level *level =
    + &sabi_config->performance_levels[i];
    + if (!strncasecmp(level->name, buf, strlen(level->name))) {
    + sabi_set_command(sabi_config->commands.set_performance_level,
    + level->value);
    + break;
    + }
    + }
    + if (!sabi_config->performance_levels[i].name)
    + return -EINVAL;
    + }
    + return count;
    +}
    +static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
    + get_performance_level, set_performance_level);
    +
    +
    +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 = "N128",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
    + DMI_MATCH(DMI_BOARD_NAME, "N128"),
    + },
    + .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,
    + },
    + {
    + .ident = "X125",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
    + DMI_MATCH(DMI_BOARD_NAME, "X125"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "NC10",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
    + DMI_MATCH(DMI_BOARD_NAME, "NC10"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "NP-Q45",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
    + DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "X360",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
    + DMI_MATCH(DMI_BOARD_NAME, "X360"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "R518",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
    + DMI_MATCH(DMI_BOARD_NAME, "R518"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "N150/N210/N220",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR,
    + "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
    + DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "R530/R730",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
    + DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
    + },
    + .callback = dmi_check_cb,
    + },
    + {
    + .ident = "NF110/NF210/NF310",
    + .matches = {
    + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
    + DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
    + DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
    + },
    + .callback = dmi_check_cb,
    + },
    + { },
    +};
    +MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
    +
    +static int find_signature(void __iomem *memcheck, const char *testStr)
    +{
    + int i = 0;
    + int loca;
    +
    + for (loca = 0; loca < 0xffff; loca++) {
    + char temp = readb(memcheck + loca);
    +
    + if (temp == testStr[i]) {
    + if (i == strlen(testStr)-1)
    + break;
    + ++i;
    + } else {
    + i = 0;
    + }
    + }
    + return loca;
    +}
    +
    +static int __init samsung_init(void)
    +{
    + struct backlight_properties props;
    + struct sabi_retval sretval;
    + unsigned int ifaceP;
    + int i;
    + 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 one of the signatures in memory to find the header */
    + for (i = 0; sabi_configs[i].test_string != 0; ++i) {
    + sabi_config = &sabi_configs[i];
    + loca = find_signature(f0000_segment, sabi_config->test_string);
    + if (loca != 0xffff)
    + break;
    + }
    +
    + if (loca == 0xffff) {
    + printk(KERN_ERR "This computer does not support SABI\n");
    + goto error_no_signature;
    + }
    +
    + /* point to the SMI port Number */
    + loca += 1;
    + sabi = (f0000_segment + loca);
    +
    + if (debug) {
    + printk(KERN_DEBUG "This computer supports SABI==%x\n",
    + loca + 0xf0000 - 6);
    + printk(KERN_DEBUG "SABI header:\n");
    + printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
    + readw(sabi + sabi_config->header_offsets.port));
    + printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
    + readb(sabi + sabi_config->header_offsets.iface_func));
    + printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
    + readb(sabi + sabi_config->header_offsets.en_mem));
    + printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
    + readb(sabi + sabi_config->header_offsets.re_mem));
    + printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
    + readw(sabi + sabi_config->header_offsets.data_offset));
    + printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
    + readw(sabi + sabi_config->header_offsets.data_segment));
    + }
    +
    + /* Get a pointer to the SABI Interface */
    + ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4;
    + ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff;
    + sabi_iface = ioremap(ifaceP, 16);
    + if (!sabi_iface) {
    + printk(KERN_ERR "Can't remap %x\n", ifaceP);
    + goto exit;
    + }
    + if (debug) {
    + printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
    + printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface);
    +
    + test_backlight();
    + test_wireless();
    +
    + retval = sabi_get_command(sabi_config->commands.get_brightness,
    + &sretval);
    + printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
    + }
    +
    + /* Turn on "Linux" mode in the BIOS */
    + if (sabi_config->commands.set_linux != 0xff) {
    + retval = sabi_set_command(sabi_config->commands.set_linux,
    + 0x81);
    + if (retval) {
    + printk(KERN_ERR KBUILD_MODNAME ": Linux mode was not set!\n");
    + goto error_no_platform;
    + }
    + }
    +
    + /* 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 */
    + memset(&props, 0, sizeof(struct backlight_properties));
    + props.max_brightness = MAX_BRIGHT;
    + backlight_device = backlight_device_register("samsung", &sdev->dev,
    + NULL, &backlight_ops,
    + &props);
    + if (IS_ERR(backlight_device))
    + goto error_no_backlight;
    +
    + backlight_device->props.brightness = read_brightness();
    + backlight_device->props.power = FB_BLANK_UNBLANK;
    + backlight_update_status(backlight_device);
    +
    + retval = init_wireless(sdev);
    + if (retval)
    + goto error_no_rfk;
    +
    + retval = device_create_file(&sdev->dev, &dev_attr_performance_level);
    + if (retval)
    + goto error_file_create;
    +
    +exit:
    + return 0;
    +
    +error_file_create:
    + destroy_wireless();
    +
    +error_no_rfk:
    + backlight_device_unregister(backlight_device);
    +
    +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)
    +{
    + /* Turn off "Linux" mode in the BIOS */
    + if (sabi_config->commands.set_linux != 0xff)
    + sabi_set_command(sabi_config->commands.set_linux, 0x80);
    +
    + device_remove_file(&sdev->dev, &dev_attr_performance_level);
    + backlight_device_unregister(backlight_device);
    + destroy_wireless();
    + 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");

    \
     
     \ /
      Last update: 2011-02-09 23:43    [W:0.068 / U:0.280 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site