1/* 2 * Samsung N130 Laptop driver 3 * 4 * Copyright (C) 2009 Greg Kroah-Hartman (gregkh@suse.de) 5 * Copyright (C) 2009 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#include <linux/kernel.h> 13#include <linux/init.h> 14#include <linux/module.h> 15#include <linux/delay.h> 16#include <linux/pci.h> 17#include <linux/backlight.h> 18#include <linux/fb.h> 19#include <linux/dmi.h> 20#include <linux/platform_device.h> 21#include <linux/rfkill.h> 22 23/* 24 * This driver is needed because a number of Samsung laptops do not hook 25 * their control settings through ACPI. So we have to poke around in the 26 * BIOS to do things like brightness values, and "special" key controls. 27 */ 28 29/* 30 * We have 0 - 8 as valid brightness levels. The specs say that level 0 should 31 * be reserved by the BIOS (which really doesn't make much sense), we tell 32 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 33 */ 34#define MAX_BRIGHT 0x07 35 36/* Brightness is 0 - 8, as described above. Value 0 is for the BIOS to use */ 37#define GET_BRIGHTNESS 0x00 38#define SET_BRIGHTNESS 0x01 39 40/* first byte: 41 * 0x00 - wireless is off 42 * 0x01 - wireless is on 43 * second byte: 44 * 0x02 - 3G is off 45 * 0x03 - 3G is on 46 * TODO, verify 3G is correct, that doesn't seem right... 47 */ 48#define GET_WIRELESS_BUTTON 0x02 49#define SET_WIRELESS_BUTTON 0x03 50 51/* 0 is off, 1 is on */ 52#define GET_BACKLIGHT 0x04 53#define SET_BACKLIGHT 0x05 54 55/* 56 * 0x80 or 0x00 - no action 57 * 0x81 - recovery key pressed 58 */ 59#define GET_RECOVERY_METHOD 0x06 60#define SET_RECOVERY_METHOD 0x07 61 62/* 0 is low, 1 is high */ 63#define GET_PERFORMANCE_LEVEL 0x08 64#define SET_PERFORMANCE_LEVEL 0x09 65 66/* 67 * Tell the BIOS that Linux is running on this machine. 68 * 81 is on, 80 is off 69 */ 70#define SET_LINUX 0x0a 71 72 73#define MAIN_FUNCTION 0x4c49 74 75#define SABI_HEADER_PORT 0x00 76#define SABI_HEADER_RE_MEM 0x02 77#define SABI_HEADER_IFACEFUNC 0x03 78#define SABI_HEADER_EN_MEM 0x04 79#define SABI_HEADER_DATA_OFFSET 0x05 80#define SABI_HEADER_DATA_SEGMENT 0x07 81 82#define SABI_IFACE_MAIN 0x00 83#define SABI_IFACE_SUB 0x02 84#define SABI_IFACE_COMPLETE 0x04 85#define SABI_IFACE_DATA 0x05 86 87/* Structure to get data back to the calling function */ 88struct sabi_retval { 89 u8 retval[20]; 90}; 91 92static void __iomem *sabi; 93static void __iomem *sabi_iface; 94static void __iomem *f0000_segment; 95static struct backlight_device *backlight_device; 96static struct mutex sabi_mutex; 97static struct platform_device *sdev; 98static struct rfkill *rfk; 99 100static int force; 101module_param(force, bool, 0); 102MODULE_PARM_DESC(force, 103 "Disable the DMI check and forces the driver to be loaded"); 104 105static int debug; 106module_param(debug, bool, S_IRUGO | S_IWUSR); 107MODULE_PARM_DESC(debug, "Debug enabled or not"); 108 109static int sabi_get_command(u8 command, struct sabi_retval *sretval) 110{ 111 int retval = 0; 112 u16 port = readw(sabi + SABI_HEADER_PORT); 113 114 mutex_lock(&sabi_mutex); 115 116 /* enable memory to be able to write to it */ 117 outb(readb(sabi + SABI_HEADER_EN_MEM), port); 118 119 /* write out the command */ 120 writew(MAIN_FUNCTION, sabi_iface + SABI_IFACE_MAIN); 121 writew(command, sabi_iface + SABI_IFACE_SUB); 122 writeb(0, sabi_iface + SABI_IFACE_COMPLETE); 123 outb(readb(sabi + SABI_HEADER_IFACEFUNC), port); 124 125 /* write protect memory to make it safe */ 126 outb(readb(sabi + SABI_HEADER_RE_MEM), port); 127 128 /* see if the command actually succeeded */ 129 if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa && 130 readb(sabi_iface + SABI_IFACE_DATA) != 0xff) { 131 /* 132 * It did! 133 * Save off the data into a structure so the caller use it. 134 * Right now we only care about the first 4 bytes, 135 * I suppose there are commands that need more, but I don't 136 * know about them. 137 */ 138 sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); 139 sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); 140 sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); 141 sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); 142 goto exit; 143 } 144 145 /* Something bad happened, so report it and error out */ 146 printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n", 147 command, readb(sabi_iface + SABI_IFACE_COMPLETE), 148 readb(sabi_iface + SABI_IFACE_DATA)); 149 retval = -EINVAL; 150exit: 151 mutex_unlock(&sabi_mutex); 152 return retval; 153 154} 155 156static int sabi_set_command(u8 command, u8 data) 157{ 158 int retval = 0; 159 u16 port = readw(sabi + SABI_HEADER_PORT); 160 161 mutex_lock(&sabi_mutex); 162 163 /* enable memory to be able to write to it */ 164 outb(readb(sabi + SABI_HEADER_EN_MEM), port); 165 166 /* write out the command */ 167 writew(MAIN_FUNCTION, sabi_iface + SABI_IFACE_MAIN); 168 writew(command, sabi_iface + SABI_IFACE_SUB); 169 writeb(0, sabi_iface + SABI_IFACE_COMPLETE); 170 writeb(data, sabi_iface + SABI_IFACE_DATA); 171 outb(readb(sabi + SABI_HEADER_IFACEFUNC), port); 172 173 /* write protect memory to make it safe */ 174 outb(readb(sabi + SABI_HEADER_RE_MEM), port); 175 176 /* see if the command actually succeeded */ 177 if (readb(sabi_iface + SABI_IFACE_COMPLETE) == 0xaa && 178 readb(sabi_iface + SABI_IFACE_DATA) != 0xff) { 179 /* it did! */ 180 goto exit; 181 } 182 183 /* Something bad happened, so report it and error out */ 184 printk(KERN_WARNING "SABI command 0x%02x failed with completion flag 0x%02x and output 0x%02x\n", 185 command, readb(sabi_iface + SABI_IFACE_COMPLETE), 186 readb(sabi_iface + SABI_IFACE_DATA)); 187 retval = -EINVAL; 188exit: 189 mutex_unlock(&sabi_mutex); 190 return retval; 191} 192 193static void test_backlight(void) 194{ 195 struct sabi_retval sretval; 196 197 sabi_get_command(GET_BACKLIGHT, &sretval); 198 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 199 200 sabi_set_command(SET_BACKLIGHT, 0); 201 printk(KERN_DEBUG "backlight should be off\n"); 202 203 sabi_get_command(GET_BACKLIGHT, &sretval); 204 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 205 206 msleep(1000); 207 208 sabi_set_command(SET_BACKLIGHT, 1); 209 printk(KERN_DEBUG "backlight should be on\n"); 210 211 sabi_get_command(GET_BACKLIGHT, &sretval); 212 printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); 213} 214 215static void test_wireless(void) 216{ 217 struct sabi_retval sretval; 218 219 sabi_get_command(GET_WIRELESS_BUTTON, &sretval); 220 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 221 222 sabi_set_command(SET_WIRELESS_BUTTON, 0); 223 printk(KERN_DEBUG "wireless led should be off\n"); 224 225 sabi_get_command(GET_WIRELESS_BUTTON, &sretval); 226 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 227 228 msleep(1000); 229 230 sabi_set_command(SET_WIRELESS_BUTTON, 1); 231 printk(KERN_DEBUG "wireless led should be on\n"); 232 233 sabi_get_command(GET_WIRELESS_BUTTON, &sretval); 234 printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); 235} 236 237static u8 read_brightness(void) 238{ 239 struct sabi_retval sretval; 240 int user_brightness = 0; 241 int retval; 242 243 retval = sabi_get_command(GET_BRIGHTNESS, &sretval); 244 if (!retval) 245 user_brightness = sretval.retval[0]; 246 if (user_brightness != 0) 247 --user_brightness; 248 return user_brightness; 249} 250 251static void set_brightness(u8 user_brightness) 252{ 253 sabi_set_command(SET_BRIGHTNESS, user_brightness + 1); 254} 255 256static int get_brightness(struct backlight_device *bd) 257{ 258 return (int)read_brightness(); 259} 260 261static int update_status(struct backlight_device *bd) 262{ 263 set_brightness(bd->props.brightness); 264 265 if (bd->props.power == FB_BLANK_UNBLANK) 266 sabi_set_command(SET_BACKLIGHT, 1); 267 else 268 sabi_set_command(SET_BACKLIGHT, 0); 269 return 0; 270} 271 272static struct backlight_ops backlight_ops = { 273 .get_brightness = get_brightness, 274 .update_status = update_status, 275}; 276 277static int rfkill_set(void *data, bool blocked) 278{ 279 /* Do something with blocked...*/ 280 /* 281 * blocked == false is on 282 * blocked == true is off 283 */ 284 if (blocked) 285 sabi_set_command(SET_WIRELESS_BUTTON, 0); 286 else 287 sabi_set_command(SET_WIRELESS_BUTTON, 1); 288 289 return 0; 290} 291 292static struct rfkill_ops rfkill_ops = { 293 .set_block = rfkill_set, 294}; 295 296static int init_wireless(struct platform_device *sdev) 297{ 298 int retval; 299 300 rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, 301 &rfkill_ops, NULL); 302 if (!rfk) 303 return -ENOMEM; 304 305 retval = rfkill_register(rfk); 306 if (retval) { 307 rfkill_destroy(rfk); 308 return -ENODEV; 309 } 310 311 return 0; 312} 313 314static void destroy_wireless(void) 315{ 316 rfkill_unregister(rfk); 317 rfkill_destroy(rfk); 318} 319 320static ssize_t get_silent_state(struct device *dev, 321 struct device_attribute *attr, char *buf) 322{ 323 struct sabi_retval sretval; 324 int retval; 325 326 /* Read the state */ 327 retval = sabi_get_command(GET_PERFORMANCE_LEVEL, &sretval); 328 if (retval) 329 return retval; 330 331 /* The logic is backwards, yeah, lots of fun... */ 332 if (sretval.retval[0] == 0) 333 retval = 1; 334 else 335 retval = 0; 336 return sprintf(buf, "%d\n", retval); 337} 338 339static ssize_t set_silent_state(struct device *dev, 340 struct device_attribute *attr, const char *buf, 341 size_t count) 342{ 343 char value; 344 345 if (count >= 1) { 346 value = buf[0]; 347 if ((value == '0') || (value == 'n') || (value == 'N')) { 348 /* Turn speed up */ 349 sabi_set_command(SET_PERFORMANCE_LEVEL, 0x01); 350 } else if ((value == '1') || (value == 'y') || (value == 'Y')) { 351 /* Turn speed down */ 352 sabi_set_command(SET_PERFORMANCE_LEVEL, 0x00); 353 } else { 354 return -EINVAL; 355 } 356 } 357 return count; 358} 359static DEVICE_ATTR(silent, S_IWUSR | S_IRUGO, 360 get_silent_state, set_silent_state); 361 362 363static int __init dmi_check_cb(const struct dmi_system_id *id) 364{ 365 printk(KERN_INFO KBUILD_MODNAME ": found laptop model '%s'\n", 366 id->ident); 367 return 0; 368} 369 370static struct dmi_system_id __initdata samsung_dmi_table[] = { 371 { 372 .ident = "N128", 373 .matches = { 374 DMI_MATCH(DMI_SYS_VENDOR, 375 "SAMSUNG ELECTRONICS CO., LTD."), 376 DMI_MATCH(DMI_PRODUCT_NAME, "N128"), 377 DMI_MATCH(DMI_BOARD_NAME, "N128"), 378 }, 379 .callback = dmi_check_cb, 380 }, 381 { 382 .ident = "N130", 383 .matches = { 384 DMI_MATCH(DMI_SYS_VENDOR, 385 "SAMSUNG ELECTRONICS CO., LTD."), 386 DMI_MATCH(DMI_PRODUCT_NAME, "N130"), 387 DMI_MATCH(DMI_BOARD_NAME, "N130"), 388 }, 389 .callback = dmi_check_cb, 390 }, 391 { }, 392}; 393MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); 394 395static int __init samsung_init(void) 396{ 397 struct backlight_properties props; 398 struct sabi_retval sretval; 399 const char *testStr = "SECLINUX"; 400 void __iomem *memcheck; 401 unsigned int ifaceP; 402 int pStr; 403 int loca; 404 int retval; 405 406 mutex_init(&sabi_mutex); 407 408 if (!force && !dmi_check_system(samsung_dmi_table)) 409 return -ENODEV; 410 411 f0000_segment = ioremap(0xf0000, 0xffff); 412 if (!f0000_segment) { 413 printk(KERN_ERR "Can't map the segment at 0xf0000\n"); 414 return -EINVAL; 415 } 416 417 /* Try to find the signature "SECLINUX" in memory to find the header */ 418 pStr = 0; 419 memcheck = f0000_segment; 420 for (loca = 0; loca < 0xffff; loca++) { 421 char temp = readb(memcheck + loca); 422 423 if (temp == testStr[pStr]) { 424 if (pStr == strlen(testStr)-1) 425 break; 426 ++pStr; 427 } else { 428 pStr = 0; 429 } 430 } 431 if (loca == 0xffff) { 432 printk(KERN_ERR "This computer does not support SABI\n"); 433 goto error_no_signature; 434 } 435 436 /* point to the SMI port Number */ 437 loca += 1; 438 sabi = (memcheck + loca); 439 440 if (debug) { 441 printk(KERN_DEBUG "This computer supports SABI==%x\n", 442 loca + 0xf0000 - 6); 443 printk(KERN_DEBUG "SABI header:\n"); 444 printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", 445 readw(sabi + SABI_HEADER_PORT)); 446 printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", 447 readb(sabi + SABI_HEADER_IFACEFUNC)); 448 printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", 449 readb(sabi + SABI_HEADER_EN_MEM)); 450 printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", 451 readb(sabi + SABI_HEADER_RE_MEM)); 452 printk(KERN_DEBUG " SABI data offset = 0x%04x\n", 453 readw(sabi + SABI_HEADER_DATA_OFFSET)); 454 printk(KERN_DEBUG " SABI data segment = 0x%04x\n", 455 readw(sabi + SABI_HEADER_DATA_SEGMENT)); 456 } 457 458 /* Get a pointer to the SABI Interface */ 459 ifaceP = (readw(sabi + SABI_HEADER_DATA_SEGMENT) & 0x0ffff) << 4; 460 ifaceP += readw(sabi + SABI_HEADER_DATA_OFFSET) & 0x0ffff; 461 sabi_iface = ioremap(ifaceP, 16); 462 if (!sabi_iface) { 463 printk(KERN_ERR "Can't remap %x\n", ifaceP); 464 goto exit; 465 } 466 if (debug) { 467 printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); 468 printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); 469 470 test_backlight(); 471 test_wireless(); 472 473 retval = sabi_get_command(GET_BRIGHTNESS, &sretval); 474 printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); 475 } 476 477 /* Turn on "Linux" mode in the BIOS */ 478 retval = sabi_set_command(SET_LINUX, 0x81); 479 if (retval) { 480 printk(KERN_ERR KBUILD_MODNAME ": Linux mode was not set!\n"); 481 goto error_no_platform; 482 } 483 484 /* knock up a platform device to hang stuff off of */ 485 sdev = platform_device_register_simple("samsung", -1, NULL, 0); 486 if (IS_ERR(sdev)) 487 goto error_no_platform; 488 489 /* create a backlight device to talk to this one */ 490 memset(&props, 0, sizeof(struct backlight_properties)); 491 props.max_brightness = MAX_BRIGHT; 492 backlight_device = backlight_device_register("samsung", &sdev->dev, 493 NULL, &backlight_ops, 494 &props); 495 if (IS_ERR(backlight_device)) 496 goto error_no_backlight; 497 498 backlight_device->props.brightness = read_brightness(); 499 backlight_device->props.power = FB_BLANK_UNBLANK; 500 backlight_update_status(backlight_device); 501 502 retval = init_wireless(sdev); 503 if (retval) 504 goto error_no_rfk; 505 506 retval = device_create_file(&sdev->dev, &dev_attr_silent); 507 if (retval) 508 goto error_file_create; 509 510exit: 511 return 0; 512 513error_file_create: 514 destroy_wireless(); 515 516error_no_rfk: 517 backlight_device_unregister(backlight_device); 518 519error_no_backlight: 520 platform_device_unregister(sdev); 521 522error_no_platform: 523 iounmap(sabi_iface); 524 525error_no_signature: 526 iounmap(f0000_segment); 527 return -EINVAL; 528} 529 530static void __exit samsung_exit(void) 531{ 532 /* Turn off "Linux" mode in the BIOS */ 533 sabi_set_command(SET_LINUX, 0x80); 534 535 device_remove_file(&sdev->dev, &dev_attr_silent); 536 backlight_device_unregister(backlight_device); 537 destroy_wireless(); 538 iounmap(sabi_iface); 539 iounmap(f0000_segment); 540 platform_device_unregister(sdev); 541} 542 543module_init(samsung_init); 544module_exit(samsung_exit); 545 546MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); 547MODULE_DESCRIPTION("Samsung Backlight driver"); 548MODULE_LICENSE("GPL"); 549