Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Platform: add Samsung Laptop platform driver

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

Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Matthew Garrett <mjg@redhat.com>

authored by

Greg Kroah-Hartman and committed by
Matthew Garrett
2d70b73a 298f19b2

+865
+19
Documentation/ABI/testing/sysfs-driver-samsung-laptop
··· 1 + What: /sys/devices/platform/samsung/performance_level 2 + Date: January 1, 2010 3 + KernelVersion: 2.6.33 4 + Contact: Greg Kroah-Hartman <gregkh@suse.de> 5 + Description: Some Samsung laptops have different "performance levels" 6 + that are can be modified by a function key, and by this 7 + sysfs file. These values don't always make a whole lot 8 + of sense, but some users like to modify them to keep 9 + their fans quiet at all costs. Reading from this file 10 + will show the current performance level. Writing to the 11 + file can change this value. 12 + Valid options: 13 + "silent" 14 + "normal" 15 + "overclock" 16 + Note that not all laptops support all of these options. 17 + Specifically, not all support the "overclock" option, 18 + and it's still unknown if this value even changes 19 + anything, other than making the user feel a bit better.
+13
drivers/platform/x86/Kconfig
··· 739 739 This switch is triggered as the screen is rotated and folded down to 740 740 convert the device into ebook form. 741 741 742 + config SAMSUNG_LAPTOP 743 + tristate "Samsung Laptop driver" 744 + depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86 745 + ---help--- 746 + This module implements a driver for a wide range of different 747 + Samsung laptops. It offers control over the different 748 + function keys, wireless LED, LCD backlight level, and 749 + sometimes provides a "performance_control" sysfs file to allow 750 + the performance level of the laptop to be changed. 751 + 752 + To compile this driver as a module, choose M here: the module 753 + will be called samsung-laptop. 754 + 742 755 endif # X86_PLATFORM_DEVICES
+1
drivers/platform/x86/Makefile
··· 40 40 obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o 41 41 obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o 42 42 obj-$(CONFIG_IBM_RTL) += ibm_rtl.o 43 + obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o 43 44 obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
+832
drivers/platform/x86/samsung-laptop.c
··· 1 + /* 2 + * Samsung Laptop driver 3 + * 4 + * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) 5 + * Copyright (C) 2009,2011 Novell Inc. 6 + * 7 + * This program is free software; you can redistribute it and/or modify it 8 + * under the terms of the GNU General Public License version 2 as published by 9 + * the Free Software Foundation. 10 + * 11 + */ 12 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 + 14 + #include <linux/kernel.h> 15 + #include <linux/init.h> 16 + #include <linux/module.h> 17 + #include <linux/delay.h> 18 + #include <linux/pci.h> 19 + #include <linux/backlight.h> 20 + #include <linux/fb.h> 21 + #include <linux/dmi.h> 22 + #include <linux/platform_device.h> 23 + #include <linux/rfkill.h> 24 + 25 + /* 26 + * This driver is needed because a number of Samsung laptops do not hook 27 + * their control settings through ACPI. So we have to poke around in the 28 + * BIOS to do things like brightness values, and "special" key controls. 29 + */ 30 + 31 + /* 32 + * We have 0 - 8 as valid brightness levels. The specs say that level 0 should 33 + * be reserved by the BIOS (which really doesn't make much sense), we tell 34 + * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 35 + */ 36 + #define MAX_BRIGHT 0x07 37 + 38 + 39 + #define SABI_IFACE_MAIN 0x00 40 + #define SABI_IFACE_SUB 0x02 41 + #define SABI_IFACE_COMPLETE 0x04 42 + #define SABI_IFACE_DATA 0x05 43 + 44 + /* Structure to get data back to the calling function */ 45 + struct sabi_retval { 46 + u8 retval[20]; 47 + }; 48 + 49 + struct sabi_header_offsets { 50 + u8 port; 51 + u8 re_mem; 52 + u8 iface_func; 53 + u8 en_mem; 54 + u8 data_offset; 55 + u8 data_segment; 56 + }; 57 + 58 + struct sabi_commands { 59 + /* 60 + * Brightness is 0 - 8, as described above. 61 + * Value 0 is for the BIOS to use 62 + */ 63 + u8 get_brightness; 64 + u8 set_brightness; 65 + 66 + /* 67 + * first byte: 68 + * 0x00 - wireless is off 69 + * 0x01 - wireless is on 70 + * second byte: 71 + * 0x02 - 3G is off 72 + * 0x03 - 3G is on 73 + * TODO, verify 3G is correct, that doesn't seem right... 74 + */ 75 + u8 get_wireless_button; 76 + u8 set_wireless_button; 77 + 78 + /* 0 is off, 1 is on */ 79 + u8 get_backlight; 80 + u8 set_backlight; 81 + 82 + /* 83 + * 0x80 or 0x00 - no action 84 + * 0x81 - recovery key pressed 85 + */ 86 + u8 get_recovery_mode; 87 + u8 set_recovery_mode; 88 + 89 + /* 90 + * on seclinux: 0 is low, 1 is high, 91 + * on swsmi: 0 is normal, 1 is silent, 2 is turbo 92 + */ 93 + u8 get_performance_level; 94 + u8 set_performance_level; 95 + 96 + /* 97 + * Tell the BIOS that Linux is running on this machine. 98 + * 81 is on, 80 is off 99 + */ 100 + u8 set_linux; 101 + }; 102 + 103 + struct sabi_performance_level { 104 + const char *name; 105 + u8 value; 106 + }; 107 + 108 + struct sabi_config { 109 + const char *test_string; 110 + u16 main_function; 111 + const struct sabi_header_offsets header_offsets; 112 + const struct sabi_commands commands; 113 + const struct sabi_performance_level performance_levels[4]; 114 + u8 min_brightness; 115 + u8 max_brightness; 116 + }; 117 + 118 + static const struct sabi_config sabi_configs[] = { 119 + { 120 + .test_string = "SECLINUX", 121 + 122 + .main_function = 0x4c49, 123 + 124 + .header_offsets = { 125 + .port = 0x00, 126 + .re_mem = 0x02, 127 + .iface_func = 0x03, 128 + .en_mem = 0x04, 129 + .data_offset = 0x05, 130 + .data_segment = 0x07, 131 + }, 132 + 133 + .commands = { 134 + .get_brightness = 0x00, 135 + .set_brightness = 0x01, 136 + 137 + .get_wireless_button = 0x02, 138 + .set_wireless_button = 0x03, 139 + 140 + .get_backlight = 0x04, 141 + .set_backlight = 0x05, 142 + 143 + .get_recovery_mode = 0x06, 144 + .set_recovery_mode = 0x07, 145 + 146 + .get_performance_level = 0x08, 147 + .set_performance_level = 0x09, 148 + 149 + .set_linux = 0x0a, 150 + }, 151 + 152 + .performance_levels = { 153 + { 154 + .name = "silent", 155 + .value = 0, 156 + }, 157 + { 158 + .name = "normal", 159 + .value = 1, 160 + }, 161 + { }, 162 + }, 163 + .min_brightness = 1, 164 + .max_brightness = 8, 165 + }, 166 + { 167 + .test_string = "SwSmi@", 168 + 169 + .main_function = 0x5843, 170 + 171 + .header_offsets = { 172 + .port = 0x00, 173 + .re_mem = 0x04, 174 + .iface_func = 0x02, 175 + .en_mem = 0x03, 176 + .data_offset = 0x05, 177 + .data_segment = 0x07, 178 + }, 179 + 180 + .commands = { 181 + .get_brightness = 0x10, 182 + .set_brightness = 0x11, 183 + 184 + .get_wireless_button = 0x12, 185 + .set_wireless_button = 0x13, 186 + 187 + .get_backlight = 0x2d, 188 + .set_backlight = 0x2e, 189 + 190 + .get_recovery_mode = 0xff, 191 + .set_recovery_mode = 0xff, 192 + 193 + .get_performance_level = 0x31, 194 + .set_performance_level = 0x32, 195 + 196 + .set_linux = 0xff, 197 + }, 198 + 199 + .performance_levels = { 200 + { 201 + .name = "normal", 202 + .value = 0, 203 + }, 204 + { 205 + .name = "silent", 206 + .value = 1, 207 + }, 208 + { 209 + .name = "overclock", 210 + .value = 2, 211 + }, 212 + { }, 213 + }, 214 + .min_brightness = 0, 215 + .max_brightness = 8, 216 + }, 217 + { }, 218 + }; 219 + 220 + static const struct sabi_config *sabi_config; 221 + 222 + static void __iomem *sabi; 223 + static void __iomem *sabi_iface; 224 + static void __iomem *f0000_segment; 225 + static struct backlight_device *backlight_device; 226 + static struct mutex sabi_mutex; 227 + static struct platform_device *sdev; 228 + static struct rfkill *rfk; 229 + 230 + static int force; 231 + module_param(force, bool, 0); 232 + MODULE_PARM_DESC(force, 233 + "Disable the DMI check and forces the driver to be loaded"); 234 + 235 + static int debug; 236 + module_param(debug, bool, S_IRUGO | S_IWUSR); 237 + MODULE_PARM_DESC(debug, "Debug enabled or not"); 238 + 239 + static int sabi_get_command(u8 command, struct sabi_retval *sretval) 240 + { 241 + int retval = 0; 242 + u16 port = readw(sabi + sabi_config->header_offsets.port); 243 + u8 complete, iface_data; 244 + 245 + mutex_lock(&sabi_mutex); 246 + 247 + /* enable memory to be able to write to it */ 248 + outb(readb(sabi + sabi_config->header_offsets.en_mem), port); 249 + 250 + /* write out the command */ 251 + writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); 252 + writew(command, sabi_iface + SABI_IFACE_SUB); 253 + writeb(0, sabi_iface + SABI_IFACE_COMPLETE); 254 + outb(readb(sabi + sabi_config->header_offsets.iface_func), port); 255 + 256 + /* write protect memory to make it safe */ 257 + outb(readb(sabi + sabi_config->header_offsets.re_mem), port); 258 + 259 + /* see if the command actually succeeded */ 260 + complete = readb(sabi_iface + SABI_IFACE_COMPLETE); 261 + iface_data = readb(sabi_iface + SABI_IFACE_DATA); 262 + if (complete != 0xaa || iface_data == 0xff) { 263 + pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", 264 + command, complete, iface_data); 265 + retval = -EINVAL; 266 + goto exit; 267 + } 268 + /* 269 + * Save off the data into a structure so the caller use it. 270 + * Right now we only want the first 4 bytes, 271 + * There are commands that need more, but not for the ones we 272 + * currently care about. 273 + */ 274 + sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); 275 + sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); 276 + sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); 277 + sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); 278 + 279 + exit: 280 + mutex_unlock(&sabi_mutex); 281 + return retval; 282 + 283 + } 284 + 285 + static int sabi_set_command(u8 command, u8 data) 286 + { 287 + int retval = 0; 288 + u16 port = readw(sabi + sabi_config->header_offsets.port); 289 + u8 complete, iface_data; 290 + 291 + mutex_lock(&sabi_mutex); 292 + 293 + /* enable memory to be able to write to it */ 294 + outb(readb(sabi + sabi_config->header_offsets.en_mem), port); 295 + 296 + /* write out the command */ 297 + writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); 298 + writew(command, sabi_iface + SABI_IFACE_SUB); 299 + writeb(0, sabi_iface + SABI_IFACE_COMPLETE); 300 + writeb(data, sabi_iface + SABI_IFACE_DATA); 301 + outb(readb(sabi + sabi_config->header_offsets.iface_func), port); 302 + 303 + /* write protect memory to make it safe */ 304 + outb(readb(sabi + sabi_config->header_offsets.re_mem), port); 305 + 306 + /* see if the command actually succeeded */ 307 + complete = readb(sabi_iface + SABI_IFACE_COMPLETE); 308 + iface_data = readb(sabi_iface + SABI_IFACE_DATA); 309 + if (complete != 0xaa || iface_data == 0xff) { 310 + pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", 311 + command, complete, iface_data); 312 + retval = -EINVAL; 313 + } 314 + 315 + mutex_unlock(&sabi_mutex); 316 + return retval; 317 + } 318 + 319 + static void test_backlight(void) 320 + { 321 + struct sabi_retval sretval; 322 + 323 + sabi_get_command(sabi_config->commands.get_backlight, &sretval); 324 + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 325 + 326 + sabi_set_command(sabi_config->commands.set_backlight, 0); 327 + printk(KERN_DEBUG "backlight should be off\n"); 328 + 329 + sabi_get_command(sabi_config->commands.get_backlight, &sretval); 330 + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 331 + 332 + msleep(1000); 333 + 334 + sabi_set_command(sabi_config->commands.set_backlight, 1); 335 + printk(KERN_DEBUG "backlight should be on\n"); 336 + 337 + sabi_get_command(sabi_config->commands.get_backlight, &sretval); 338 + printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 339 + } 340 + 341 + static void test_wireless(void) 342 + { 343 + struct sabi_retval sretval; 344 + 345 + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); 346 + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 347 + 348 + sabi_set_command(sabi_config->commands.set_wireless_button, 0); 349 + printk(KERN_DEBUG "wireless led should be off\n"); 350 + 351 + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); 352 + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 353 + 354 + msleep(1000); 355 + 356 + sabi_set_command(sabi_config->commands.set_wireless_button, 1); 357 + printk(KERN_DEBUG "wireless led should be on\n"); 358 + 359 + sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); 360 + printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 361 + } 362 + 363 + static u8 read_brightness(void) 364 + { 365 + struct sabi_retval sretval; 366 + int user_brightness = 0; 367 + int retval; 368 + 369 + retval = sabi_get_command(sabi_config->commands.get_brightness, 370 + &sretval); 371 + if (!retval) { 372 + user_brightness = sretval.retval[0]; 373 + if (user_brightness != 0) 374 + user_brightness -= sabi_config->min_brightness; 375 + } 376 + return user_brightness; 377 + } 378 + 379 + static void set_brightness(u8 user_brightness) 380 + { 381 + u8 user_level = user_brightness - sabi_config->min_brightness; 382 + 383 + sabi_set_command(sabi_config->commands.set_brightness, user_level); 384 + } 385 + 386 + static int get_brightness(struct backlight_device *bd) 387 + { 388 + return (int)read_brightness(); 389 + } 390 + 391 + static int update_status(struct backlight_device *bd) 392 + { 393 + set_brightness(bd->props.brightness); 394 + 395 + if (bd->props.power == FB_BLANK_UNBLANK) 396 + sabi_set_command(sabi_config->commands.set_backlight, 1); 397 + else 398 + sabi_set_command(sabi_config->commands.set_backlight, 0); 399 + return 0; 400 + } 401 + 402 + static const struct backlight_ops backlight_ops = { 403 + .get_brightness = get_brightness, 404 + .update_status = update_status, 405 + }; 406 + 407 + static int rfkill_set(void *data, bool blocked) 408 + { 409 + /* Do something with blocked...*/ 410 + /* 411 + * blocked == false is on 412 + * blocked == true is off 413 + */ 414 + if (blocked) 415 + sabi_set_command(sabi_config->commands.set_wireless_button, 0); 416 + else 417 + sabi_set_command(sabi_config->commands.set_wireless_button, 1); 418 + 419 + return 0; 420 + } 421 + 422 + static struct rfkill_ops rfkill_ops = { 423 + .set_block = rfkill_set, 424 + }; 425 + 426 + static int init_wireless(struct platform_device *sdev) 427 + { 428 + int retval; 429 + 430 + rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, 431 + &rfkill_ops, NULL); 432 + if (!rfk) 433 + return -ENOMEM; 434 + 435 + retval = rfkill_register(rfk); 436 + if (retval) { 437 + rfkill_destroy(rfk); 438 + return -ENODEV; 439 + } 440 + 441 + return 0; 442 + } 443 + 444 + static void destroy_wireless(void) 445 + { 446 + rfkill_unregister(rfk); 447 + rfkill_destroy(rfk); 448 + } 449 + 450 + static ssize_t get_performance_level(struct device *dev, 451 + struct device_attribute *attr, char *buf) 452 + { 453 + struct sabi_retval sretval; 454 + int retval; 455 + int i; 456 + 457 + /* Read the state */ 458 + retval = sabi_get_command(sabi_config->commands.get_performance_level, 459 + &sretval); 460 + if (retval) 461 + return retval; 462 + 463 + /* The logic is backwards, yeah, lots of fun... */ 464 + for (i = 0; sabi_config->performance_levels[i].name; ++i) { 465 + if (sretval.retval[0] == sabi_config->performance_levels[i].value) 466 + return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); 467 + } 468 + return sprintf(buf, "%s\n", "unknown"); 469 + } 470 + 471 + static ssize_t set_performance_level(struct device *dev, 472 + struct device_attribute *attr, const char *buf, 473 + size_t count) 474 + { 475 + if (count >= 1) { 476 + int i; 477 + for (i = 0; sabi_config->performance_levels[i].name; ++i) { 478 + const struct sabi_performance_level *level = 479 + &sabi_config->performance_levels[i]; 480 + if (!strncasecmp(level->name, buf, strlen(level->name))) { 481 + sabi_set_command(sabi_config->commands.set_performance_level, 482 + level->value); 483 + break; 484 + } 485 + } 486 + if (!sabi_config->performance_levels[i].name) 487 + return -EINVAL; 488 + } 489 + return count; 490 + } 491 + static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, 492 + get_performance_level, set_performance_level); 493 + 494 + 495 + static int __init dmi_check_cb(const struct dmi_system_id *id) 496 + { 497 + pr_info("found laptop model '%s'\n", 498 + id->ident); 499 + return 0; 500 + } 501 + 502 + static struct dmi_system_id __initdata samsung_dmi_table[] = { 503 + { 504 + .ident = "N128", 505 + .matches = { 506 + DMI_MATCH(DMI_SYS_VENDOR, 507 + "SAMSUNG ELECTRONICS CO., LTD."), 508 + DMI_MATCH(DMI_PRODUCT_NAME, "N128"), 509 + DMI_MATCH(DMI_BOARD_NAME, "N128"), 510 + }, 511 + .callback = dmi_check_cb, 512 + }, 513 + { 514 + .ident = "N130", 515 + .matches = { 516 + DMI_MATCH(DMI_SYS_VENDOR, 517 + "SAMSUNG ELECTRONICS CO., LTD."), 518 + DMI_MATCH(DMI_PRODUCT_NAME, "N130"), 519 + DMI_MATCH(DMI_BOARD_NAME, "N130"), 520 + }, 521 + .callback = dmi_check_cb, 522 + }, 523 + { 524 + .ident = "X125", 525 + .matches = { 526 + DMI_MATCH(DMI_SYS_VENDOR, 527 + "SAMSUNG ELECTRONICS CO., LTD."), 528 + DMI_MATCH(DMI_PRODUCT_NAME, "X125"), 529 + DMI_MATCH(DMI_BOARD_NAME, "X125"), 530 + }, 531 + .callback = dmi_check_cb, 532 + }, 533 + { 534 + .ident = "X120/X170", 535 + .matches = { 536 + DMI_MATCH(DMI_SYS_VENDOR, 537 + "SAMSUNG ELECTRONICS CO., LTD."), 538 + DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), 539 + DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), 540 + }, 541 + .callback = dmi_check_cb, 542 + }, 543 + { 544 + .ident = "NC10", 545 + .matches = { 546 + DMI_MATCH(DMI_SYS_VENDOR, 547 + "SAMSUNG ELECTRONICS CO., LTD."), 548 + DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), 549 + DMI_MATCH(DMI_BOARD_NAME, "NC10"), 550 + }, 551 + .callback = dmi_check_cb, 552 + }, 553 + { 554 + .ident = "NP-Q45", 555 + .matches = { 556 + DMI_MATCH(DMI_SYS_VENDOR, 557 + "SAMSUNG ELECTRONICS CO., LTD."), 558 + DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), 559 + DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), 560 + }, 561 + .callback = dmi_check_cb, 562 + }, 563 + { 564 + .ident = "X360", 565 + .matches = { 566 + DMI_MATCH(DMI_SYS_VENDOR, 567 + "SAMSUNG ELECTRONICS CO., LTD."), 568 + DMI_MATCH(DMI_PRODUCT_NAME, "X360"), 569 + DMI_MATCH(DMI_BOARD_NAME, "X360"), 570 + }, 571 + .callback = dmi_check_cb, 572 + }, 573 + { 574 + .ident = "R518", 575 + .matches = { 576 + DMI_MATCH(DMI_SYS_VENDOR, 577 + "SAMSUNG ELECTRONICS CO., LTD."), 578 + DMI_MATCH(DMI_PRODUCT_NAME, "R518"), 579 + DMI_MATCH(DMI_BOARD_NAME, "R518"), 580 + }, 581 + .callback = dmi_check_cb, 582 + }, 583 + { 584 + .ident = "R519/R719", 585 + .matches = { 586 + DMI_MATCH(DMI_SYS_VENDOR, 587 + "SAMSUNG ELECTRONICS CO., LTD."), 588 + DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), 589 + DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), 590 + }, 591 + .callback = dmi_check_cb, 592 + }, 593 + { 594 + .ident = "N150/N210/N220", 595 + .matches = { 596 + DMI_MATCH(DMI_SYS_VENDOR, 597 + "SAMSUNG ELECTRONICS CO., LTD."), 598 + DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), 599 + DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), 600 + }, 601 + .callback = dmi_check_cb, 602 + }, 603 + { 604 + .ident = "N150P/N210P/N220P", 605 + .matches = { 606 + DMI_MATCH(DMI_SYS_VENDOR, 607 + "SAMSUNG ELECTRONICS CO., LTD."), 608 + DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), 609 + DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), 610 + }, 611 + .callback = dmi_check_cb, 612 + }, 613 + { 614 + .ident = "R530/R730", 615 + .matches = { 616 + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), 617 + DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), 618 + DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), 619 + }, 620 + .callback = dmi_check_cb, 621 + }, 622 + { 623 + .ident = "NF110/NF210/NF310", 624 + .matches = { 625 + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), 626 + DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), 627 + DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), 628 + }, 629 + .callback = dmi_check_cb, 630 + }, 631 + { 632 + .ident = "N145P/N250P/N260P", 633 + .matches = { 634 + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), 635 + DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), 636 + DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), 637 + }, 638 + .callback = dmi_check_cb, 639 + }, 640 + { 641 + .ident = "R70/R71", 642 + .matches = { 643 + DMI_MATCH(DMI_SYS_VENDOR, 644 + "SAMSUNG ELECTRONICS CO., LTD."), 645 + DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), 646 + DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), 647 + }, 648 + .callback = dmi_check_cb, 649 + }, 650 + { 651 + .ident = "P460", 652 + .matches = { 653 + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), 654 + DMI_MATCH(DMI_PRODUCT_NAME, "P460"), 655 + DMI_MATCH(DMI_BOARD_NAME, "P460"), 656 + }, 657 + .callback = dmi_check_cb, 658 + }, 659 + { }, 660 + }; 661 + MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); 662 + 663 + static int find_signature(void __iomem *memcheck, const char *testStr) 664 + { 665 + int i = 0; 666 + int loca; 667 + 668 + for (loca = 0; loca < 0xffff; loca++) { 669 + char temp = readb(memcheck + loca); 670 + 671 + if (temp == testStr[i]) { 672 + if (i == strlen(testStr)-1) 673 + break; 674 + ++i; 675 + } else { 676 + i = 0; 677 + } 678 + } 679 + return loca; 680 + } 681 + 682 + static int __init samsung_init(void) 683 + { 684 + struct backlight_properties props; 685 + struct sabi_retval sretval; 686 + unsigned int ifaceP; 687 + int i; 688 + int loca; 689 + int retval; 690 + 691 + mutex_init(&sabi_mutex); 692 + 693 + if (!force && !dmi_check_system(samsung_dmi_table)) 694 + return -ENODEV; 695 + 696 + f0000_segment = ioremap_nocache(0xf0000, 0xffff); 697 + if (!f0000_segment) { 698 + pr_err("Can't map the segment at 0xf0000\n"); 699 + return -EINVAL; 700 + } 701 + 702 + /* Try to find one of the signatures in memory to find the header */ 703 + for (i = 0; sabi_configs[i].test_string != 0; ++i) { 704 + sabi_config = &sabi_configs[i]; 705 + loca = find_signature(f0000_segment, sabi_config->test_string); 706 + if (loca != 0xffff) 707 + break; 708 + } 709 + 710 + if (loca == 0xffff) { 711 + pr_err("This computer does not support SABI\n"); 712 + goto error_no_signature; 713 + } 714 + 715 + /* point to the SMI port Number */ 716 + loca += 1; 717 + sabi = (f0000_segment + loca); 718 + 719 + if (debug) { 720 + printk(KERN_DEBUG "This computer supports SABI==%x\n", 721 + loca + 0xf0000 - 6); 722 + printk(KERN_DEBUG "SABI header:\n"); 723 + printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", 724 + readw(sabi + sabi_config->header_offsets.port)); 725 + printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", 726 + readb(sabi + sabi_config->header_offsets.iface_func)); 727 + printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", 728 + readb(sabi + sabi_config->header_offsets.en_mem)); 729 + printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", 730 + readb(sabi + sabi_config->header_offsets.re_mem)); 731 + printk(KERN_DEBUG " SABI data offset = 0x%04x\n", 732 + readw(sabi + sabi_config->header_offsets.data_offset)); 733 + printk(KERN_DEBUG " SABI data segment = 0x%04x\n", 734 + readw(sabi + sabi_config->header_offsets.data_segment)); 735 + } 736 + 737 + /* Get a pointer to the SABI Interface */ 738 + ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; 739 + ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; 740 + sabi_iface = ioremap_nocache(ifaceP, 16); 741 + if (!sabi_iface) { 742 + pr_err("Can't remap %x\n", ifaceP); 743 + goto exit; 744 + } 745 + if (debug) { 746 + printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); 747 + printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); 748 + 749 + test_backlight(); 750 + test_wireless(); 751 + 752 + retval = sabi_get_command(sabi_config->commands.get_brightness, 753 + &sretval); 754 + printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); 755 + } 756 + 757 + /* Turn on "Linux" mode in the BIOS */ 758 + if (sabi_config->commands.set_linux != 0xff) { 759 + retval = sabi_set_command(sabi_config->commands.set_linux, 760 + 0x81); 761 + if (retval) { 762 + pr_warn("Linux mode was not set!\n"); 763 + goto error_no_platform; 764 + } 765 + } 766 + 767 + /* knock up a platform device to hang stuff off of */ 768 + sdev = platform_device_register_simple("samsung", -1, NULL, 0); 769 + if (IS_ERR(sdev)) 770 + goto error_no_platform; 771 + 772 + /* create a backlight device to talk to this one */ 773 + memset(&props, 0, sizeof(struct backlight_properties)); 774 + props.max_brightness = sabi_config->max_brightness; 775 + backlight_device = backlight_device_register("samsung", &sdev->dev, 776 + NULL, &backlight_ops, 777 + &props); 778 + if (IS_ERR(backlight_device)) 779 + goto error_no_backlight; 780 + 781 + backlight_device->props.brightness = read_brightness(); 782 + backlight_device->props.power = FB_BLANK_UNBLANK; 783 + backlight_update_status(backlight_device); 784 + 785 + retval = init_wireless(sdev); 786 + if (retval) 787 + goto error_no_rfk; 788 + 789 + retval = device_create_file(&sdev->dev, &dev_attr_performance_level); 790 + if (retval) 791 + goto error_file_create; 792 + 793 + exit: 794 + return 0; 795 + 796 + error_file_create: 797 + destroy_wireless(); 798 + 799 + error_no_rfk: 800 + backlight_device_unregister(backlight_device); 801 + 802 + error_no_backlight: 803 + platform_device_unregister(sdev); 804 + 805 + error_no_platform: 806 + iounmap(sabi_iface); 807 + 808 + error_no_signature: 809 + iounmap(f0000_segment); 810 + return -EINVAL; 811 + } 812 + 813 + static void __exit samsung_exit(void) 814 + { 815 + /* Turn off "Linux" mode in the BIOS */ 816 + if (sabi_config->commands.set_linux != 0xff) 817 + sabi_set_command(sabi_config->commands.set_linux, 0x80); 818 + 819 + device_remove_file(&sdev->dev, &dev_attr_performance_level); 820 + backlight_device_unregister(backlight_device); 821 + destroy_wireless(); 822 + iounmap(sabi_iface); 823 + iounmap(f0000_segment); 824 + platform_device_unregister(sdev); 825 + } 826 + 827 + module_init(samsung_init); 828 + module_exit(samsung_exit); 829 + 830 + MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); 831 + MODULE_DESCRIPTION("Samsung Backlight driver"); 832 + MODULE_LICENSE("GPL");