Created
January 28, 2012 16:30
-
-
Save lkraav/1694937 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig | |
index 7f43cf8..17e2fa1 100644 | |
--- a/drivers/platform/x86/Kconfig | |
+++ b/drivers/platform/x86/Kconfig | |
@@ -744,13 +742,18 @@ config XO15_EBOOK | |
config SAMSUNG_LAPTOP | |
tristate "Samsung Laptop driver" | |
- depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86 | |
+ depends on X86 | |
+ depends on RFKILL || RFKILL = n | |
+ depends on BACKLIGHT_CLASS_DEVICE | |
+ select LEDS_CLASS | |
+ select NEW_LEDS | |
---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. | |
+ function keys, wireless LED, LCD backlight level. | |
+ | |
+ It may also provide some sysfs files described in | |
+ <file:Documentation/ABI/testing/sysfs-platform-samsung-laptop> | |
To compile this driver as a module, choose M here: the module | |
will be called samsung-laptop. | |
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c | |
index 09e26bf..dae1244 100644 | |
--- a/drivers/platform/x86/samsung-laptop.c | |
+++ b/drivers/platform/x86/samsung-laptop.c | |
@@ -17,10 +17,14 @@ | |
#include <linux/delay.h> | |
#include <linux/pci.h> | |
#include <linux/backlight.h> | |
+#include <linux/leds.h> | |
#include <linux/fb.h> | |
#include <linux/dmi.h> | |
#include <linux/platform_device.h> | |
#include <linux/rfkill.h> | |
+#include <linux/acpi.h> | |
+#include <linux/seq_file.h> | |
+#include <linux/debugfs.h> | |
/* | |
* This driver is needed because a number of Samsung laptops do not hook | |
@@ -41,9 +45,20 @@ | |
#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]; | |
+#define WL_STATUS_WLAN 0x0 | |
+#define WL_STATUS_BT 0x2 | |
+ | |
+/* Structure get/set data using sabi */ | |
+struct sabi_data { | |
+ union { | |
+ struct { | |
+ u32 d0; | |
+ u32 d1; | |
+ u16 d2; | |
+ u8 d3; | |
+ }; | |
+ u8 data[11]; | |
+ }; | |
}; | |
struct sabi_header_offsets { | |
@@ -60,8 +75,8 @@ struct sabi_commands { | |
* Brightness is 0 - 8, as described above. | |
* Value 0 is for the BIOS to use | |
*/ | |
- u8 get_brightness; | |
- u8 set_brightness; | |
+ u16 get_brightness; | |
+ u16 set_brightness; | |
/* | |
* first byte: | |
@@ -72,40 +87,56 @@ struct sabi_commands { | |
* 0x03 - 3G is on | |
* TODO, verify 3G is correct, that doesn't seem right... | |
*/ | |
- u8 get_wireless_button; | |
- u8 set_wireless_button; | |
+ u16 get_wireless_button; | |
+ u16 set_wireless_button; | |
/* 0 is off, 1 is on */ | |
- u8 get_backlight; | |
- u8 set_backlight; | |
+ u16 get_backlight; | |
+ u16 set_backlight; | |
/* | |
* 0x80 or 0x00 - no action | |
* 0x81 - recovery key pressed | |
*/ | |
- u8 get_recovery_mode; | |
- u8 set_recovery_mode; | |
+ u16 get_recovery_mode; | |
+ u16 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; | |
+ u16 get_performance_level; | |
+ u16 set_performance_level; | |
+ | |
+ /* 0x80 is off, 0x81 is on */ | |
+ u16 get_battery_life_extender; | |
+ u16 set_battery_life_extender; | |
+ | |
+ /* 0x80 is off, 0x81 is on */ | |
+ u16 get_usb_charge; | |
+ u16 set_usb_charge; | |
+ | |
+ /* the first byte is for bluetooth and the third one is for wlan */ | |
+ u16 get_wireless_status; | |
+ u16 set_wireless_status; | |
+ | |
+ /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */ | |
+ u16 kbd_backlight; | |
/* | |
* Tell the BIOS that Linux is running on this machine. | |
* 81 is on, 80 is off | |
*/ | |
- u8 set_linux; | |
+ u16 set_linux; | |
}; | |
struct sabi_performance_level { | |
const char *name; | |
- u8 value; | |
+ u16 value; | |
}; | |
struct sabi_config { | |
+ int sabi_version; | |
const char *test_string; | |
u16 main_function; | |
const struct sabi_header_offsets header_offsets; | |
@@ -117,6 +148,10 @@ struct sabi_config { | |
static const struct sabi_config sabi_configs[] = { | |
{ | |
+ /* I don't know if it is really 2, but it it is | |
+ * less than 3 anyway */ | |
+ .sabi_version = 2, | |
+ | |
.test_string = "SECLINUX", | |
.main_function = 0x4c49, | |
@@ -146,6 +181,17 @@ static const struct sabi_config sabi_configs[] = { | |
.get_performance_level = 0x08, | |
.set_performance_level = 0x09, | |
+ .get_battery_life_extender = 0xFFFF, | |
+ .set_battery_life_extender = 0xFFFF, | |
+ | |
+ .get_usb_charge = 0xFFFF, | |
+ .set_usb_charge = 0xFFFF, | |
+ | |
+ .get_wireless_status = 0xFFFF, | |
+ .set_wireless_status = 0xFFFF, | |
+ | |
+ .kbd_backlight = 0xFFFF, | |
+ | |
.set_linux = 0x0a, | |
}, | |
@@ -164,6 +210,8 @@ static const struct sabi_config sabi_configs[] = { | |
.max_brightness = 8, | |
}, | |
{ | |
+ .sabi_version = 3, | |
+ | |
.test_string = "SwSmi@", | |
.main_function = 0x5843, | |
@@ -193,6 +241,17 @@ static const struct sabi_config sabi_configs[] = { | |
.get_performance_level = 0x31, | |
.set_performance_level = 0x32, | |
+ .get_battery_life_extender = 0x65, | |
+ .set_battery_life_extender = 0x66, | |
+ | |
+ .get_usb_charge = 0x67, | |
+ .set_usb_charge = 0x68, | |
+ | |
+ .get_wireless_status = 0x69, | |
+ .set_wireless_status = 0x6a, | |
+ | |
+ .kbd_backlight = 0x78, | |
+ | |
.set_linux = 0xff, | |
}, | |
@@ -217,16 +276,73 @@ static const struct sabi_config sabi_configs[] = { | |
{ }, | |
}; | |
-static const struct sabi_config *sabi_config; | |
+/* | |
+ * samsung-laptop/ - debugfs root directory | |
+ * f0000_segment - dump f0000 segment | |
+ * command - current command | |
+ * data - current data | |
+ * d0, d1, d2, d3 - data fields | |
+ * call - call SABI using command and data | |
+ * | |
+ * This allow to call arbitrary sabi commands wihout | |
+ * modifying the driver at all. | |
+ * For example, setting the keyboard backlight brightness to 5 | |
+ * | |
+ * echo 0x78 > command | |
+ * echo 0x0582 > d0 | |
+ * echo 0 > d1 | |
+ * echo 0 > d2 | |
+ * echo 0 > d3 | |
+ * cat call | |
+ */ | |
+ | |
+struct samsung_laptop_debug { | |
+ struct dentry *root; | |
+ struct sabi_data data; | |
+ u16 command; | |
+ | |
+ struct debugfs_blob_wrapper f0000_wrapper; | |
+ struct debugfs_blob_wrapper data_wrapper; | |
+ struct debugfs_blob_wrapper sdiag_wrapper; | |
+}; | |
+ | |
+struct samsung_laptop; | |
+ | |
+struct samsung_rfkill { | |
+ struct samsung_laptop *samsung; | |
+ struct rfkill *rfkill; | |
+ enum rfkill_type type; | |
+}; | |
+ | |
+struct samsung_laptop { | |
+ const struct sabi_config *config; | |
+ | |
+ void __iomem *sabi; | |
+ void __iomem *sabi_iface; | |
+ void __iomem *f0000_segment; | |
+ | |
+ struct mutex sabi_mutex; | |
+ | |
+ struct platform_device *platform_device; | |
+ struct backlight_device *backlight_device; | |
+ | |
+ struct samsung_rfkill wlan; | |
+ struct samsung_rfkill bluetooth; | |
+ | |
+ struct led_classdev kbd_led; | |
+ int kbd_led_wk; | |
+ struct workqueue_struct *led_workqueue; | |
+ struct work_struct kbd_led_work; | |
+ | |
+ struct samsung_laptop_debug debug; | |
+ | |
+ bool handle_backlight; | |
+ bool has_stepping_quirk; | |
+ | |
+ char sdiag[64]; | |
+}; | |
+ | |
-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 bool has_stepping_quirk; | |
static int force; | |
module_param(force, bool, 0); | |
@@ -237,176 +353,143 @@ 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) | |
+static int sabi_command(struct samsung_laptop *samsung, u16 command, | |
+ struct sabi_data *in, | |
+ struct sabi_data *out) | |
{ | |
- int retval = 0; | |
- u16 port = readw(sabi + sabi_config->header_offsets.port); | |
+ const struct sabi_config *config = samsung->config; | |
+ int ret = 0; | |
+ u16 port = readw(samsung->sabi + config->header_offsets.port); | |
u8 complete, iface_data; | |
- 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); | |
+ mutex_lock(&samsung->sabi_mutex); | |
- /* see if the command actually succeeded */ | |
- complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
- iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
- if (complete != 0xaa || iface_data == 0xff) { | |
- pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
- command, complete, iface_data); | |
- retval = -EINVAL; | |
- goto exit; | |
+ if (debug) { | |
+ if (in) | |
+ pr_info("SABI command:0x%04x " | |
+ "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", | |
+ command, in->d0, in->d1, in->d2, in->d3); | |
+ else | |
+ pr_info("SABI command:0x%04x", command); | |
} | |
- /* | |
- * Save off the data into a structure so the caller use it. | |
- * Right now we only want the first 4 bytes, | |
- * There are commands that need more, but not for the ones we | |
- * currently care about. | |
- */ | |
- 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); | |
- | |
-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); | |
- u8 complete, iface_data; | |
- | |
- mutex_lock(&sabi_mutex); | |
/* enable memory to be able to write to it */ | |
- outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | |
+ outb(readb(samsung->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); | |
+ writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN); | |
+ writew(command, samsung->sabi_iface + SABI_IFACE_SUB); | |
+ writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE); | |
+ if (in) { | |
+ writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA); | |
+ writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4); | |
+ writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8); | |
+ writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10); | |
+ } | |
+ outb(readb(samsung->sabi + config->header_offsets.iface_func), port); | |
/* write protect memory to make it safe */ | |
- outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | |
+ outb(readb(samsung->sabi + config->header_offsets.re_mem), port); | |
/* see if the command actually succeeded */ | |
- complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | |
- iface_data = readb(sabi_iface + SABI_IFACE_DATA); | |
+ complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE); | |
+ iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA); | |
+ | |
+ /* iface_data = 0xFF happens when a command is not known | |
+ * so we only add a warning in debug mode since we will | |
+ * probably issue some unknown command at startup to find | |
+ * out which features are supported */ | |
+ if (complete != 0xaa || (iface_data == 0xff && debug)) | |
+ pr_warn("SABI command 0x%04x failed with" | |
+ " completion flag 0x%02x and interface data 0x%02x", | |
+ command, complete, iface_data); | |
+ | |
if (complete != 0xaa || iface_data == 0xff) { | |
- pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | |
- command, complete, iface_data); | |
- retval = -EINVAL; | |
+ ret = -EINVAL; | |
+ goto 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); | |
+ if (out) { | |
+ out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA); | |
+ out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4); | |
+ out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2); | |
+ out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1); | |
+ } | |
- sabi_set_command(sabi_config->commands.set_backlight, 1); | |
- printk(KERN_DEBUG "backlight should be on\n"); | |
+ if (debug && out) { | |
+ pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}", | |
+ out->d0, out->d1, out->d2, out->d3); | |
+ } | |
- sabi_get_command(sabi_config->commands.get_backlight, &sretval); | |
- printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | |
+exit: | |
+ mutex_unlock(&samsung->sabi_mutex); | |
+ return ret; | |
} | |
-static void test_wireless(void) | |
+/* simple wrappers usable with most commands */ | |
+static int sabi_set_commandb(struct samsung_laptop *samsung, | |
+ u16 command, u8 data) | |
{ | |
- 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]); | |
+ struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 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]); | |
+ in.data[0] = data; | |
+ return sabi_command(samsung, command, &in, NULL); | |
} | |
-static u8 read_brightness(void) | |
+static int read_brightness(struct samsung_laptop *samsung) | |
{ | |
- struct sabi_retval sretval; | |
+ const struct sabi_config *config = samsung->config; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data 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 > sabi_config->min_brightness) | |
- user_brightness -= sabi_config->min_brightness; | |
- else | |
- user_brightness = 0; | |
- } | |
+ retval = sabi_command(samsung, commands->get_brightness, | |
+ NULL, &sretval); | |
+ if (retval) | |
+ return retval; | |
+ | |
+ user_brightness = sretval.data[0]; | |
+ if (user_brightness > config->min_brightness) | |
+ user_brightness -= config->min_brightness; | |
+ else | |
+ user_brightness = 0; | |
+ | |
return user_brightness; | |
} | |
-static void set_brightness(u8 user_brightness) | |
+static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness) | |
{ | |
- u8 user_level = user_brightness + sabi_config->min_brightness; | |
+ const struct sabi_config *config = samsung->config; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ u8 user_level = user_brightness + config->min_brightness; | |
- if (has_stepping_quirk && user_level != 0) { | |
+ if (samsung->has_stepping_quirk && user_level != 0) { | |
/* | |
* short circuit if the specified level is what's already set | |
* to prevent the screen from flickering needlessly | |
*/ | |
- if (user_brightness == read_brightness()) | |
+ if (user_brightness == read_brightness(samsung)) | |
return; | |
- sabi_set_command(sabi_config->commands.set_brightness, 0); | |
+ sabi_set_commandb(samsung, commands->set_brightness, 0); | |
} | |
- sabi_set_command(sabi_config->commands.set_brightness, user_level); | |
+ sabi_set_commandb(samsung, commands->set_brightness, user_level); | |
} | |
static int get_brightness(struct backlight_device *bd) | |
{ | |
- return (int)read_brightness(); | |
+ struct samsung_laptop *samsung = bl_get_data(bd); | |
+ | |
+ return read_brightness(samsung); | |
} | |
-static void check_for_stepping_quirk(void) | |
+static void check_for_stepping_quirk(struct samsung_laptop *samsung) | |
{ | |
- u8 initial_level; | |
- u8 check_level; | |
- u8 orig_level = read_brightness(); | |
+ int initial_level; | |
+ int check_level; | |
+ int orig_level = read_brightness(samsung); | |
/* | |
* Some laptops exhibit the strange behaviour of stepping toward | |
@@ -416,34 +499,38 @@ static void check_for_stepping_quirk(void) | |
*/ | |
if (orig_level == 0) | |
- set_brightness(1); | |
+ set_brightness(samsung, 1); | |
- initial_level = read_brightness(); | |
+ initial_level = read_brightness(samsung); | |
if (initial_level <= 2) | |
check_level = initial_level + 2; | |
else | |
check_level = initial_level - 2; | |
- has_stepping_quirk = false; | |
- set_brightness(check_level); | |
+ samsung->has_stepping_quirk = false; | |
+ set_brightness(samsung, check_level); | |
- if (read_brightness() != check_level) { | |
- has_stepping_quirk = true; | |
+ if (read_brightness(samsung) != check_level) { | |
+ samsung->has_stepping_quirk = true; | |
pr_info("enabled workaround for brightness stepping quirk\n"); | |
} | |
- set_brightness(orig_level); | |
+ set_brightness(samsung, orig_level); | |
} | |
static int update_status(struct backlight_device *bd) | |
{ | |
- set_brightness(bd->props.brightness); | |
+ struct samsung_laptop *samsung = bl_get_data(bd); | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ | |
+ set_brightness(samsung, bd->props.brightness); | |
if (bd->props.power == FB_BLANK_UNBLANK) | |
- sabi_set_command(sabi_config->commands.set_backlight, 1); | |
+ sabi_set_commandb(samsung, commands->set_backlight, 1); | |
else | |
- sabi_set_command(sabi_config->commands.set_backlight, 0); | |
+ sabi_set_commandb(samsung, commands->set_backlight, 0); | |
+ | |
return 0; | |
} | |
@@ -452,66 +539,101 @@ static const struct backlight_ops backlight_ops = { | |
.update_status = update_status, | |
}; | |
-static int rfkill_set(void *data, bool blocked) | |
+static int seclinux_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); | |
+ struct samsung_rfkill *srfkill = data; | |
+ struct samsung_laptop *samsung = srfkill->samsung; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
- return 0; | |
+ return sabi_set_commandb(samsung, commands->set_wireless_button, | |
+ !blocked); | |
} | |
-static struct rfkill_ops rfkill_ops = { | |
- .set_block = rfkill_set, | |
+static struct rfkill_ops seclinux_rfkill_ops = { | |
+ .set_block = seclinux_rfkill_set, | |
}; | |
-static int init_wireless(struct platform_device *sdev) | |
+static int swsmi_wireless_status(struct samsung_laptop *samsung, | |
+ struct sabi_data *data) | |
{ | |
- int retval; | |
- | |
- rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | |
- &rfkill_ops, NULL); | |
- if (!rfk) | |
- return -ENOMEM; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
- retval = rfkill_register(rfk); | |
- if (retval) { | |
- rfkill_destroy(rfk); | |
- return -ENODEV; | |
- } | |
+ return sabi_command(samsung, commands->get_wireless_status, | |
+ NULL, data); | |
+} | |
- return 0; | |
+static int swsmi_rfkill_set(void *priv, bool blocked) | |
+{ | |
+ struct samsung_rfkill *srfkill = priv; | |
+ struct samsung_laptop *samsung = srfkill->samsung; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ int ret, i; | |
+ | |
+ ret = swsmi_wireless_status(samsung, &data); | |
+ if (ret) | |
+ return ret; | |
+ | |
+ /* Don't set the state for non-present devices */ | |
+ for (i = 0; i < 4; i++) | |
+ if (data.data[i] == 0x02) | |
+ data.data[1] = 0; | |
+ | |
+ if (srfkill->type == RFKILL_TYPE_WLAN) | |
+ data.data[WL_STATUS_WLAN] = !blocked; | |
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) | |
+ data.data[WL_STATUS_BT] = !blocked; | |
+ | |
+ return sabi_command(samsung, commands->set_wireless_status, | |
+ &data, &data); | |
} | |
-static void destroy_wireless(void) | |
+static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv) | |
{ | |
- rfkill_unregister(rfk); | |
- rfkill_destroy(rfk); | |
+ struct samsung_rfkill *srfkill = priv; | |
+ struct samsung_laptop *samsung = srfkill->samsung; | |
+ struct sabi_data data; | |
+ int ret; | |
+ | |
+ ret = swsmi_wireless_status(samsung, &data); | |
+ if (ret) | |
+ return ; | |
+ | |
+ if (srfkill->type == RFKILL_TYPE_WLAN) | |
+ ret = data.data[WL_STATUS_WLAN]; | |
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH) | |
+ ret = data.data[WL_STATUS_BT]; | |
+ else | |
+ return ; | |
+ | |
+ rfkill_set_sw_state(rfkill, !ret); | |
} | |
+static struct rfkill_ops swsmi_rfkill_ops = { | |
+ .set_block = swsmi_rfkill_set, | |
+ .query = swsmi_rfkill_query, | |
+}; | |
+ | |
static ssize_t get_performance_level(struct device *dev, | |
struct device_attribute *attr, char *buf) | |
{ | |
- struct sabi_retval sretval; | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ const struct sabi_config *config = samsung->config; | |
+ const struct sabi_commands *commands = &config->commands; | |
+ struct sabi_data sretval; | |
int retval; | |
int i; | |
/* Read the state */ | |
- retval = sabi_get_command(sabi_config->commands.get_performance_level, | |
- &sretval); | |
+ retval = sabi_command(samsung, commands->get_performance_level, | |
+ NULL, &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); | |
+ for (i = 0; config->performance_levels[i].name; ++i) { | |
+ if (sretval.data[0] == config->performance_levels[i].value) | |
+ return sprintf(buf, "%s\n", config->performance_levels[i].name); | |
} | |
return sprintf(buf, "%s\n", "unknown"); | |
} | |
@@ -520,274 +642,183 @@ 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) { | |
- const 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; | |
- } | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ const struct sabi_config *config = samsung->config; | |
+ const struct sabi_commands *commands = &config->commands; | |
+ int i; | |
+ | |
+ if (count < 1) | |
+ return count; | |
+ | |
+ for (i = 0; config->performance_levels[i].name; ++i) { | |
+ const struct sabi_performance_level *level = | |
+ &config->performance_levels[i]; | |
+ if (!strncasecmp(level->name, buf, strlen(level->name))) { | |
+ sabi_set_commandb(samsung, | |
+ commands->set_performance_level, | |
+ level->value); | |
+ break; | |
} | |
- if (!sabi_config->performance_levels[i].name) | |
- return -EINVAL; | |
} | |
+ | |
+ if (!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) | |
+static int read_battery_life_extender(struct samsung_laptop *samsung) | |
{ | |
- pr_info("found laptop model '%s'\n", | |
- id->ident); | |
- return 1; | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ int retval; | |
+ | |
+ if (commands->get_battery_life_extender == 0xFFFF) | |
+ return -ENODEV; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.data[0] = 0x80; | |
+ retval = sabi_command(samsung, commands->get_battery_life_extender, | |
+ &data, &data); | |
+ | |
+ if (retval) | |
+ return retval; | |
+ | |
+ if (data.data[0] != 0 && data.data[0] != 1) | |
+ return -ENODEV; | |
+ | |
+ return data.data[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 = "N510", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "N510"), | |
- DMI_MATCH(DMI_BOARD_NAME, "N510"), | |
- }, | |
- .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 = "X120/X170", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | |
- DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | |
- }, | |
- .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 = "R410 Plus", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "R410P"), | |
- DMI_MATCH(DMI_BOARD_NAME, "R460"), | |
- }, | |
- .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 = "R519/R719", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | |
- DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | |
- }, | |
- .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 = "N220", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "N220"), | |
- DMI_MATCH(DMI_BOARD_NAME, "N220"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "N150/N210/N220/N230", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"), | |
- DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "N150P/N210P/N220P", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | |
- DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "R700", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "SR700"), | |
- DMI_MATCH(DMI_BOARD_NAME, "SR700"), | |
- }, | |
- .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, | |
- }, | |
- { | |
- .ident = "N145P/N250P/N260P", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | |
- DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "R70/R71", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, | |
- "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | |
- DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "P460", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | |
- DMI_MATCH(DMI_BOARD_NAME, "P460"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "R528/R728", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"), | |
- DMI_MATCH(DMI_BOARD_NAME, "R528/R728"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "NC210/NC110", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"), | |
- DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { | |
- .ident = "X520", | |
- .matches = { | |
- DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | |
- DMI_MATCH(DMI_PRODUCT_NAME, "X520"), | |
- DMI_MATCH(DMI_BOARD_NAME, "X520"), | |
- }, | |
- .callback = dmi_check_cb, | |
- }, | |
- { }, | |
-}; | |
-MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
+static int write_battery_life_extender(struct samsung_laptop *samsung, | |
+ int enabled) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
-static int find_signature(void __iomem *memcheck, const char *testStr) | |
+ memset(&data, 0, sizeof(data)); | |
+ data.data[0] = 0x80 | enabled; | |
+ return sabi_command(samsung, commands->set_battery_life_extender, | |
+ &data, NULL); | |
+} | |
+ | |
+static ssize_t get_battery_life_extender(struct device *dev, | |
+ struct device_attribute *attr, | |
+ char *buf) | |
{ | |
- int i = 0; | |
- int loca; | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ int ret; | |
+ | |
+ ret = read_battery_life_extender(samsung); | |
+ if (ret < 0) | |
+ return ret; | |
+ | |
+ return sprintf(buf, "%d\n", ret); | |
+} | |
+ | |
+static ssize_t set_battery_life_extender(struct device *dev, | |
+ struct device_attribute *attr, | |
+ const char *buf, size_t count) | |
+{ | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ int ret, value; | |
+ | |
+ if (!count || sscanf(buf, "%i", &value) != 1) | |
+ return -EINVAL; | |
+ | |
+ ret = write_battery_life_extender(samsung, !!value); | |
+ if (ret < 0) | |
+ return ret; | |
+ | |
+ return count; | |
+} | |
+ | |
+static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, | |
+ get_battery_life_extender, set_battery_life_extender); | |
+ | |
+static int read_usb_charge(struct samsung_laptop *samsung) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ int retval; | |
+ | |
+ if (commands->get_usb_charge == 0xFFFF) | |
+ return -ENODEV; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.data[0] = 0x80; | |
+ retval = sabi_command(samsung, commands->get_usb_charge, | |
+ &data, &data); | |
+ | |
+ if (retval) | |
+ return retval; | |
+ | |
+ if (data.data[0] != 0 && data.data[0] != 1) | |
+ return -ENODEV; | |
+ | |
+ return data.data[0]; | |
+} | |
+ | |
+static int write_usb_charge(struct samsung_laptop *samsung, | |
+ int enabled) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.data[0] = 0x80 | enabled; | |
+ return sabi_command(samsung, commands->set_usb_charge, | |
+ &data, NULL); | |
+} | |
+ | |
+static ssize_t get_usb_charge(struct device *dev, | |
+ struct device_attribute *attr, | |
+ char *buf) | |
+{ | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ int ret; | |
+ | |
+ ret = read_usb_charge(samsung); | |
+ if (ret < 0) | |
+ return ret; | |
+ | |
+ return sprintf(buf, "%d\n", ret); | |
+} | |
+ | |
+static ssize_t set_usb_charge(struct device *dev, | |
+ struct device_attribute *attr, | |
+ const char *buf, size_t count) | |
+{ | |
+ struct samsung_laptop *samsung = dev_get_drvdata(dev); | |
+ int ret, value; | |
+ | |
+ if (!count || sscanf(buf, "%i", &value) != 1) | |
+ return -EINVAL; | |
+ | |
+ ret = write_usb_charge(samsung, !!value); | |
+ if (ret < 0) | |
+ return ret; | |
+ | |
+ return count; | |
+} | |
+ | |
+static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, | |
+ get_usb_charge, set_usb_charge); | |
+ | |
+static struct attribute *platform_attributes[] = { | |
+ &dev_attr_performance_level.attr, | |
+ &dev_attr_battery_life_extender.attr, | |
+ &dev_attr_usb_charge.attr, | |
+ NULL | |
+}; | |
+ | |
+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); | |
@@ -803,153 +834,712 @@ static int find_signature(void __iomem *memcheck, const char *testStr) | |
return loca; | |
} | |
-static int __init samsung_init(void) | |
+static void samsung_rfkill_exit(struct samsung_laptop *samsung) | |
{ | |
- struct backlight_properties props; | |
- struct sabi_retval sretval; | |
- unsigned int ifaceP; | |
- int i; | |
- int loca; | |
+ if (samsung->wlan.rfkill) { | |
+ rfkill_unregister(samsung->wlan.rfkill); | |
+ rfkill_destroy(samsung->wlan.rfkill); | |
+ samsung->wlan.rfkill = NULL; | |
+ } | |
+ if (samsung->bluetooth.rfkill) { | |
+ rfkill_unregister(samsung->bluetooth.rfkill); | |
+ rfkill_destroy(samsung->bluetooth.rfkill); | |
+ samsung->bluetooth.rfkill = NULL; | |
+ } | |
+} | |
+ | |
+static int samsung_new_rfkill(struct samsung_laptop *samsung, | |
+ struct samsung_rfkill *arfkill, | |
+ const char *name, enum rfkill_type type, | |
+ const struct rfkill_ops *ops, | |
+ int blocked) | |
+{ | |
+ struct rfkill **rfkill = &arfkill->rfkill; | |
+ int ret; | |
+ | |
+ arfkill->type = type; | |
+ arfkill->samsung = samsung; | |
+ | |
+ *rfkill = rfkill_alloc(name, &samsung->platform_device->dev, | |
+ type, ops, arfkill); | |
+ | |
+ if (!*rfkill) | |
+ return -EINVAL; | |
+ | |
+ if (blocked != -1) | |
+ rfkill_init_sw_state(*rfkill, blocked); | |
+ | |
+ ret = rfkill_register(*rfkill); | |
+ if (ret) { | |
+ rfkill_destroy(*rfkill); | |
+ *rfkill = NULL; | |
+ return ret; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung) | |
+{ | |
+ return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan", | |
+ RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1); | |
+} | |
+ | |
+static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung) | |
+{ | |
+ struct sabi_data data; | |
+ int ret; | |
+ | |
+ ret = swsmi_wireless_status(samsung, &data); | |
+ if (ret) { | |
+ /* Some swsmi laptops use the old seclinux way to control | |
+ * wireless devices */ | |
+ if (ret == -EINVAL) | |
+ ret = samsung_rfkill_init_seclinux(samsung); | |
+ return ret; | |
+ } | |
+ | |
+ /* 0x02 seems to mean that the device is no present/available */ | |
+ | |
+ if (data.data[WL_STATUS_WLAN] != 0x02) | |
+ ret = samsung_new_rfkill(samsung, &samsung->wlan, | |
+ "samsung-wlan", | |
+ RFKILL_TYPE_WLAN, | |
+ &swsmi_rfkill_ops, | |
+ !data.data[WL_STATUS_WLAN]); | |
+ if (ret) | |
+ goto exit; | |
+ | |
+ if (data.data[WL_STATUS_BT] != 0x02) | |
+ ret = samsung_new_rfkill(samsung, &samsung->bluetooth, | |
+ "samsung-bluetooth", | |
+ RFKILL_TYPE_BLUETOOTH, | |
+ &swsmi_rfkill_ops, | |
+ !data.data[WL_STATUS_BT]); | |
+ if (ret) | |
+ goto exit; | |
+ | |
+exit: | |
+ if (ret) | |
+ samsung_rfkill_exit(samsung); | |
+ | |
+ return ret; | |
+} | |
+ | |
+static int __init samsung_rfkill_init(struct samsung_laptop *samsung) | |
+{ | |
+ if (samsung->config->sabi_version == 2) | |
+ return samsung_rfkill_init_seclinux(samsung); | |
+ if (samsung->config->sabi_version == 3) | |
+ return samsung_rfkill_init_swsmi(samsung); | |
+ return 0; | |
+} | |
+ | |
+static int kbd_backlight_enable(struct samsung_laptop *samsung) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
int retval; | |
- mutex_init(&sabi_mutex); | |
+ if (commands->kbd_backlight == 0xFFFF) | |
+ return -ENODEV; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.d0 = 0xaabb; | |
+ retval = sabi_command(samsung, commands->kbd_backlight, | |
+ &data, &data); | |
- if (!force && !dmi_check_system(samsung_dmi_table)) | |
+ if (retval) | |
+ return retval; | |
+ | |
+ if (data.d0 != 0xccdd) | |
return -ENODEV; | |
+ return 0; | |
+} | |
- f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
- if (!f0000_segment) { | |
- pr_err("Can't map the segment at 0xf0000\n"); | |
- return -EINVAL; | |
+static int kbd_backlight_read(struct samsung_laptop *samsung) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ int retval; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.data[0] = 0x81; | |
+ retval = sabi_command(samsung, commands->kbd_backlight, | |
+ &data, &data); | |
+ | |
+ if (retval) | |
+ return retval; | |
+ | |
+ return data.data[0]; | |
+} | |
+ | |
+static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness) | |
+{ | |
+ const struct sabi_commands *commands = &samsung->config->commands; | |
+ struct sabi_data data; | |
+ | |
+ memset(&data, 0, sizeof(data)); | |
+ data.d0 = 0x82 | ((brightness & 0xFF) << 8); | |
+ return sabi_command(samsung, commands->kbd_backlight, | |
+ &data, NULL); | |
+} | |
+ | |
+static void kbd_led_update(struct work_struct *work) | |
+{ | |
+ struct samsung_laptop *samsung; | |
+ | |
+ samsung = container_of(work, struct samsung_laptop, kbd_led_work); | |
+ kbd_backlight_write(samsung, samsung->kbd_led_wk); | |
+} | |
+ | |
+static void kbd_led_set(struct led_classdev *led_cdev, | |
+ enum led_brightness value) | |
+{ | |
+ struct samsung_laptop *samsung; | |
+ | |
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); | |
+ | |
+ if (value > samsung->kbd_led.max_brightness) | |
+ value = samsung->kbd_led.max_brightness; | |
+ else if (value < 0) | |
+ value = 0; | |
+ | |
+ samsung->kbd_led_wk = value; | |
+ queue_work(samsung->led_workqueue, &samsung->kbd_led_work); | |
+} | |
+ | |
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) | |
+{ | |
+ struct samsung_laptop *samsung; | |
+ | |
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led); | |
+ return kbd_backlight_read(samsung); | |
+} | |
+ | |
+static void samsung_leds_exit(struct samsung_laptop *samsung) | |
+{ | |
+ if (!IS_ERR_OR_NULL(samsung->kbd_led.dev)) | |
+ led_classdev_unregister(&samsung->kbd_led); | |
+ if (samsung->led_workqueue) | |
+ destroy_workqueue(samsung->led_workqueue); | |
+} | |
+ | |
+static int __init samsung_leds_init(struct samsung_laptop *samsung) | |
+{ | |
+ int ret = 0; | |
+ | |
+ samsung->led_workqueue = create_singlethread_workqueue("led_workqueue"); | |
+ if (!samsung->led_workqueue) | |
+ return -ENOMEM; | |
+ | |
+ if (kbd_backlight_enable(samsung) >= 0) { | |
+ INIT_WORK(&samsung->kbd_led_work, kbd_led_update); | |
+ | |
+ samsung->kbd_led.name = "samsung::kbd_backlight"; | |
+ samsung->kbd_led.brightness_set = kbd_led_set; | |
+ samsung->kbd_led.brightness_get = kbd_led_get; | |
+ samsung->kbd_led.max_brightness = 8; | |
+ | |
+ ret = led_classdev_register(&samsung->platform_device->dev, | |
+ &samsung->kbd_led); | |
+ } | |
+ | |
+ if (ret) | |
+ samsung_leds_exit(samsung); | |
+ | |
+ return ret; | |
+} | |
+ | |
+static void samsung_backlight_exit(struct samsung_laptop *samsung) | |
+{ | |
+ if (samsung->backlight_device) { | |
+ backlight_device_unregister(samsung->backlight_device); | |
+ samsung->backlight_device = NULL; | |
} | |
+} | |
+ | |
+static int __init samsung_backlight_init(struct samsung_laptop *samsung) | |
+{ | |
+ struct backlight_device *bd; | |
+ struct backlight_properties props; | |
+ | |
+ if (!samsung->handle_backlight) | |
+ return 0; | |
+ | |
+ memset(&props, 0, sizeof(struct backlight_properties)); | |
+ props.type = BACKLIGHT_PLATFORM; | |
+ props.max_brightness = samsung->config->max_brightness - | |
+ samsung->config->min_brightness; | |
+ | |
+ bd = backlight_device_register("samsung", | |
+ &samsung->platform_device->dev, | |
+ samsung, &backlight_ops, | |
+ &props); | |
+ if (IS_ERR(bd)) | |
+ return PTR_ERR(bd); | |
+ | |
+ samsung->backlight_device = bd; | |
+ samsung->backlight_device->props.brightness = read_brightness(samsung); | |
+ samsung->backlight_device->props.power = FB_BLANK_UNBLANK; | |
+ backlight_update_status(samsung->backlight_device); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static mode_t samsung_sysfs_is_visible(struct kobject *kobj, | |
+ struct attribute *attr, int idx) | |
+{ | |
+ struct device *dev = container_of(kobj, struct device, kobj); | |
+ struct platform_device *pdev = to_platform_device(dev); | |
+ struct samsung_laptop *samsung = platform_get_drvdata(pdev); | |
+ bool ok = true; | |
+ | |
+ if (attr == &dev_attr_performance_level.attr) | |
+ ok = !!samsung->config->performance_levels[0].name; | |
+ if (attr == &dev_attr_battery_life_extender.attr) | |
+ ok = !!(read_battery_life_extender(samsung) >= 0); | |
+ if (attr == &dev_attr_usb_charge.attr) | |
+ ok = !!(read_usb_charge(samsung) >= 0); | |
+ | |
+ return ok ? attr->mode : 0; | |
+} | |
+ | |
+static struct attribute_group platform_attribute_group = { | |
+ .is_visible = samsung_sysfs_is_visible, | |
+ .attrs = platform_attributes | |
+}; | |
+ | |
+static void samsung_sysfs_exit(struct samsung_laptop *samsung) | |
+{ | |
+ struct platform_device *device = samsung->platform_device; | |
+ | |
+ sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); | |
+} | |
+ | |
+static int __init samsung_sysfs_init(struct samsung_laptop *samsung) | |
+{ | |
+ struct platform_device *device = samsung->platform_device; | |
+ | |
+ return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); | |
+ | |
+} | |
+ | |
+static int show_call(struct seq_file *m, void *data) | |
+{ | |
+ struct samsung_laptop *samsung = m->private; | |
+ struct sabi_data *sdata = &samsung->debug.data; | |
+ int ret; | |
+ | |
+ seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | |
+ samsung->debug.command, | |
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3); | |
+ | |
+ ret = sabi_command(samsung, samsung->debug.command, sdata, sdata); | |
+ | |
+ if (ret) { | |
+ seq_printf(m, "SABI command 0x%04x failed\n", | |
+ samsung->debug.command); | |
+ return ret; | |
+ } | |
+ | |
+ seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n", | |
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3); | |
+ return 0; | |
+} | |
+ | |
+static int samsung_debugfs_open(struct inode *inode, struct file *file) | |
+{ | |
+ return single_open(file, show_call, inode->i_private); | |
+} | |
+ | |
+static const struct file_operations samsung_laptop_call_io_ops = { | |
+ .owner = THIS_MODULE, | |
+ .open = samsung_debugfs_open, | |
+ .read = seq_read, | |
+ .llseek = seq_lseek, | |
+ .release = single_release, | |
+}; | |
+ | |
+static void samsung_debugfs_exit(struct samsung_laptop *samsung) | |
+{ | |
+ debugfs_remove_recursive(samsung->debug.root); | |
+} | |
+ | |
+static int samsung_debugfs_init(struct samsung_laptop *samsung) | |
+{ | |
+ struct dentry *dent; | |
+ | |
+ samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); | |
+ if (!samsung->debug.root) { | |
+ pr_err("failed to create debugfs directory"); | |
+ goto error_debugfs; | |
+ } | |
+ | |
+ samsung->debug.f0000_wrapper.data = samsung->f0000_segment; | |
+ samsung->debug.f0000_wrapper.size = 0xffff; | |
+ | |
+ samsung->debug.data_wrapper.data = &samsung->debug.data; | |
+ samsung->debug.data_wrapper.size = sizeof(samsung->debug.data); | |
+ | |
+ samsung->debug.sdiag_wrapper.data = samsung->sdiag; | |
+ samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); | |
+ | |
+ dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, | |
+ samsung->debug.root, &samsung->debug.command); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, | |
+ &samsung->debug.data.d0); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, | |
+ &samsung->debug.data.d1); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, | |
+ &samsung->debug.data.d2); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, | |
+ &samsung->debug.data.d3); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, | |
+ samsung->debug.root, | |
+ &samsung->debug.data_wrapper); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, | |
+ samsung->debug.root, | |
+ &samsung->debug.f0000_wrapper); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_file("call", S_IFREG | S_IRUGO, | |
+ samsung->debug.root, samsung, | |
+ &samsung_laptop_call_io_ops); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, | |
+ samsung->debug.root, | |
+ &samsung->debug.sdiag_wrapper); | |
+ if (!dent) | |
+ goto error_debugfs; | |
+ | |
+ return 0; | |
+ | |
+error_debugfs: | |
+ samsung_debugfs_exit(samsung); | |
+ return -ENOMEM; | |
+} | |
+ | |
+static void samsung_sabi_exit(struct samsung_laptop *samsung) | |
+{ | |
+ const struct sabi_config *config = samsung->config; | |
+ | |
+ /* Turn off "Linux" mode in the BIOS */ | |
+ if (config && config->commands.set_linux != 0xff) | |
+ sabi_set_commandb(samsung, config->commands.set_linux, 0x80); | |
+ | |
+ if (samsung->sabi_iface) { | |
+ iounmap(samsung->sabi_iface); | |
+ samsung->sabi_iface = NULL; | |
+ } | |
+ if (samsung->f0000_segment) { | |
+ iounmap(samsung->f0000_segment); | |
+ samsung->f0000_segment = NULL; | |
+ } | |
+ | |
+ samsung->config = NULL; | |
+} | |
+ | |
+static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca, | |
+ unsigned int ifaceP) | |
+{ | |
+ const struct sabi_config *config = samsung->config; | |
+ | |
+ 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(samsung->sabi + config->header_offsets.port)); | |
+ printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | |
+ readb(samsung->sabi + config->header_offsets.iface_func)); | |
+ printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | |
+ readb(samsung->sabi + config->header_offsets.en_mem)); | |
+ printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | |
+ readb(samsung->sabi + config->header_offsets.re_mem)); | |
+ printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | |
+ readw(samsung->sabi + config->header_offsets.data_offset)); | |
+ printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | |
+ readw(samsung->sabi + config->header_offsets.data_segment)); | |
+ | |
+ printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP); | |
+} | |
+ | |
+static void __init samsung_sabi_diag(struct samsung_laptop *samsung) | |
+{ | |
+ int loca = find_signature(samsung->f0000_segment, "SDiaG@"); | |
+ int i; | |
+ | |
+ if (loca == 0xffff) | |
+ return ; | |
+ | |
+ /* Example: | |
+ * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A | |
+ * | |
+ * Product name: 90X3A | |
+ * BIOS Version: 07HL | |
+ */ | |
+ loca += 1; | |
+ for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) { | |
+ char temp = readb(samsung->f0000_segment + loca); | |
+ | |
+ if (isalnum(temp) || temp == '/' || temp == '-') | |
+ samsung->sdiag[i++] = temp; | |
+ else | |
+ break ; | |
+ } | |
+ | |
+ if (debug && samsung->sdiag[0]) | |
+ pr_info("sdiag: %s", samsung->sdiag); | |
+} | |
+ | |
+static int __init samsung_sabi_init(struct samsung_laptop *samsung) | |
+{ | |
+ const struct sabi_config *config = NULL; | |
+ const struct sabi_commands *commands; | |
+ unsigned int ifaceP; | |
+ int ret = 0; | |
+ int i; | |
+ int loca; | |
+ | |
+ samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff); | |
+ if (!samsung->f0000_segment) { | |
+ if (debug || force) | |
+ pr_err("Can't map the segment at 0xf0000\n"); | |
+ ret = -EINVAL; | |
+ goto exit; | |
+ } | |
+ | |
+ samsung_sabi_diag(samsung); | |
/* 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); | |
+ samsung->config = &sabi_configs[i]; | |
+ loca = find_signature(samsung->f0000_segment, | |
+ samsung->config->test_string); | |
if (loca != 0xffff) | |
break; | |
} | |
if (loca == 0xffff) { | |
- pr_err("This computer does not support SABI\n"); | |
- goto error_no_signature; | |
+ if (debug || force) | |
+ pr_err("This computer does not support SABI\n"); | |
+ ret = -ENODEV; | |
+ goto exit; | |
} | |
+ config = samsung->config; | |
+ commands = &config->commands; | |
+ | |
/* 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)); | |
- } | |
+ samsung->sabi = (samsung->f0000_segment + loca); | |
/* 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_nocache(ifaceP, 16); | |
- if (!sabi_iface) { | |
- pr_err("Can't remap %x\n", ifaceP); | |
- goto error_no_signature; | |
- } | |
- if (debug) { | |
- printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | |
- printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | |
+ ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4; | |
+ ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff; | |
- test_backlight(); | |
- test_wireless(); | |
+ if (debug) | |
+ samsung_sabi_infos(samsung, loca, ifaceP); | |
- retval = sabi_get_command(sabi_config->commands.get_brightness, | |
- &sretval); | |
- printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | |
+ samsung->sabi_iface = ioremap_nocache(ifaceP, 16); | |
+ if (!samsung->sabi_iface) { | |
+ pr_err("Can't remap %x\n", ifaceP); | |
+ ret = -EINVAL; | |
+ goto exit; | |
} | |
/* 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 (commands->set_linux != 0xff) { | |
+ int retval = sabi_set_commandb(samsung, | |
+ commands->set_linux, 0x81); | |
if (retval) { | |
pr_warn("Linux mode was not set!\n"); | |
- goto error_no_platform; | |
+ ret = -ENODEV; | |
+ goto exit; | |
} | |
} | |
/* Check for stepping quirk */ | |
- check_for_stepping_quirk(); | |
+ if (samsung->handle_backlight) | |
+ check_for_stepping_quirk(samsung); | |
- /* 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; | |
+ pr_info("detected SABI interface: %s\n", | |
+ samsung->config->test_string); | |
- /* create a backlight device to talk to this one */ | |
- memset(&props, 0, sizeof(struct backlight_properties)); | |
- props.type = BACKLIGHT_PLATFORM; | |
- props.max_brightness = sabi_config->max_brightness - | |
- sabi_config->min_brightness; | |
- 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; | |
+exit: | |
+ if (ret) | |
+ samsung_sabi_exit(samsung); | |
- retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | |
- if (retval) | |
- goto error_file_create; | |
+ return ret; | |
+} | |
+ | |
+static void samsung_platform_exit(struct samsung_laptop *samsung) | |
+{ | |
+ if (samsung->platform_device) { | |
+ platform_device_unregister(samsung->platform_device); | |
+ samsung->platform_device = NULL; | |
+ } | |
+} | |
+ | |
+static int __init samsung_platform_init(struct samsung_laptop *samsung) | |
+{ | |
+ struct platform_device *pdev; | |
+ pdev = platform_device_register_simple("samsung", -1, NULL, 0); | |
+ if (IS_ERR(pdev)) | |
+ return PTR_ERR(pdev); | |
+ | |
+ samsung->platform_device = pdev; | |
+ platform_set_drvdata(samsung->platform_device, samsung); | |
return 0; | |
+} | |
+ | |
+static struct dmi_system_id __initdata samsung_dmi_table[] = { | |
+ { | |
+ .matches = { | |
+ DMI_MATCH(DMI_SYS_VENDOR, | |
+ "SAMSUNG ELECTRONICS CO., LTD."), | |
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */ | |
+ }, | |
+ }, | |
+ { | |
+ .matches = { | |
+ DMI_MATCH(DMI_SYS_VENDOR, | |
+ "SAMSUNG ELECTRONICS CO., LTD."), | |
+ DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */ | |
+ }, | |
+ }, | |
+ { | |
+ .matches = { | |
+ DMI_MATCH(DMI_SYS_VENDOR, | |
+ "SAMSUNG ELECTRONICS CO., LTD."), | |
+ DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */ | |
+ }, | |
+ }, | |
+ { | |
+ .matches = { | |
+ DMI_MATCH(DMI_SYS_VENDOR, | |
+ "SAMSUNG ELECTRONICS CO., LTD."), | |
+ DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */ | |
+ }, | |
+ }, | |
+ { }, | |
+}; | |
+MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | |
-error_file_create: | |
- destroy_wireless(); | |
+static struct platform_device *samsung_platform_device; | |
-error_no_rfk: | |
- backlight_device_unregister(backlight_device); | |
+static int __init samsung_init(void) | |
+{ | |
+ struct samsung_laptop *samsung; | |
+ int ret; | |
-error_no_backlight: | |
- platform_device_unregister(sdev); | |
+ if (!force && !dmi_check_system(samsung_dmi_table)) | |
+ return -ENODEV; | |
-error_no_platform: | |
- iounmap(sabi_iface); | |
+ samsung = kzalloc(sizeof(*samsung), GFP_KERNEL); | |
+ if (!samsung) | |
+ return -ENOMEM; | |
-error_no_signature: | |
- iounmap(f0000_segment); | |
- return -EINVAL; | |
+ mutex_init(&samsung->sabi_mutex); | |
+ samsung->handle_backlight = true; | |
+ | |
+#ifdef CONFIG_ACPI | |
+ /* Don't handle backlight here if the acpi video already handle it */ | |
+ if (acpi_video_backlight_support()) | |
+ samsung->handle_backlight = false; | |
+#endif | |
+ ret = samsung_platform_init(samsung); | |
+ if (ret) | |
+ goto error_platform; | |
+ | |
+ ret = samsung_sabi_init(samsung); | |
+ if (ret) | |
+ goto error_sabi; | |
+ | |
+#ifdef CONFIG_ACPI | |
+ /* Only log that if we are really on a sabi platform */ | |
+ if (acpi_video_backlight_support()) | |
+ pr_info("Backlight controlled by ACPI video driver\n"); | |
+#endif | |
+ | |
+ ret = samsung_sysfs_init(samsung); | |
+ if (ret) | |
+ goto error_sysfs; | |
+ | |
+ ret = samsung_backlight_init(samsung); | |
+ if (ret) | |
+ goto error_backlight; | |
+ | |
+ ret = samsung_rfkill_init(samsung); | |
+ if (ret) | |
+ goto error_rfkill; | |
+ | |
+ ret = samsung_leds_init(samsung); | |
+ if (ret) | |
+ goto error_leds; | |
+ | |
+ ret = samsung_debugfs_init(samsung); | |
+ if (ret) | |
+ goto error_debugfs; | |
+ | |
+ samsung_platform_device = samsung->platform_device; | |
+ return ret; | |
+ | |
+error_debugfs: | |
+ samsung_leds_exit(samsung); | |
+error_leds: | |
+ samsung_rfkill_exit(samsung); | |
+error_rfkill: | |
+ samsung_backlight_exit(samsung); | |
+error_backlight: | |
+ samsung_sysfs_exit(samsung); | |
+error_sysfs: | |
+ samsung_sabi_exit(samsung); | |
+error_sabi: | |
+ samsung_platform_exit(samsung); | |
+error_platform: | |
+ kfree(samsung); | |
+ return ret; | |
} | |
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); | |
+ struct samsung_laptop *samsung; | |
+ | |
+ samsung = platform_get_drvdata(samsung_platform_device); | |
+ | |
+ samsung_debugfs_exit(samsung); | |
+ samsung_leds_exit(samsung); | |
+ samsung_rfkill_exit(samsung); | |
+ samsung_backlight_exit(samsung); | |
+ samsung_sysfs_exit(samsung); | |
+ samsung_sabi_exit(samsung); | |
+ samsung_platform_exit(samsung); | |
+ | |
+ kfree(samsung); | |
+ samsung_platform_device = NULL; | |
} | |
module_init(samsung_init); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment