linux/drivers/platform/x86/samsung-laptop.c
<<
>>
Prefs
   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 */
  45struct sabi_retval {
  46        u8 retval[20];
  47};
  48
  49struct 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
  58struct 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
 103struct sabi_performance_level {
 104        const char *name;
 105        u8 value;
 106};
 107
 108struct 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
 118static 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
 220static const struct sabi_config *sabi_config;
 221
 222static void __iomem *sabi;
 223static void __iomem *sabi_iface;
 224static void __iomem *f0000_segment;
 225static struct backlight_device *backlight_device;
 226static struct mutex sabi_mutex;
 227static struct platform_device *sdev;
 228static struct rfkill *rfk;
 229
 230static int force;
 231module_param(force, bool, 0);
 232MODULE_PARM_DESC(force,
 233                "Disable the DMI check and forces the driver to be loaded");
 234
 235static int debug;
 236module_param(debug, bool, S_IRUGO | S_IWUSR);
 237MODULE_PARM_DESC(debug, "Debug enabled or not");
 238
 239static 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
 279exit:
 280        mutex_unlock(&sabi_mutex);
 281        return retval;
 282
 283}
 284
 285static 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
 319static 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
 341static 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
 363static 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
 379static 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
 386static int get_brightness(struct backlight_device *bd)
 387{
 388        return (int)read_brightness();
 389}
 390
 391static 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
 402static const struct backlight_ops backlight_ops = {
 403        .get_brightness = get_brightness,
 404        .update_status  = update_status,
 405};
 406
 407static 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
 422static struct rfkill_ops rfkill_ops = {
 423        .set_block = rfkill_set,
 424};
 425
 426static 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
 444static void destroy_wireless(void)
 445{
 446        rfkill_unregister(rfk);
 447        rfkill_destroy(rfk);
 448}
 449
 450static 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
 471static 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}
 491static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
 492                   get_performance_level, set_performance_level);
 493
 494
 495static 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 1;
 500}
 501
 502static 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 = "N510",
 525                .matches = {
 526                        DMI_MATCH(DMI_SYS_VENDOR,
 527                                        "SAMSUNG ELECTRONICS CO., LTD."),
 528                        DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
 529                        DMI_MATCH(DMI_BOARD_NAME, "N510"),
 530                },
 531                .callback = dmi_check_cb,
 532        },
 533        {
 534                .ident = "X125",
 535                .matches = {
 536                        DMI_MATCH(DMI_SYS_VENDOR,
 537                                        "SAMSUNG ELECTRONICS CO., LTD."),
 538                        DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
 539                        DMI_MATCH(DMI_BOARD_NAME, "X125"),
 540                },
 541                .callback = dmi_check_cb,
 542        },
 543        {
 544                .ident = "X120/X170",
 545                .matches = {
 546                        DMI_MATCH(DMI_SYS_VENDOR,
 547                                        "SAMSUNG ELECTRONICS CO., LTD."),
 548                        DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
 549                        DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
 550                },
 551                .callback = dmi_check_cb,
 552        },
 553        {
 554                .ident = "NC10",
 555                .matches = {
 556                        DMI_MATCH(DMI_SYS_VENDOR,
 557                                        "SAMSUNG ELECTRONICS CO., LTD."),
 558                        DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
 559                        DMI_MATCH(DMI_BOARD_NAME, "NC10"),
 560                },
 561                .callback = dmi_check_cb,
 562        },
 563                {
 564                .ident = "NP-Q45",
 565                .matches = {
 566                        DMI_MATCH(DMI_SYS_VENDOR,
 567                                        "SAMSUNG ELECTRONICS CO., LTD."),
 568                        DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
 569                        DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
 570                },
 571                .callback = dmi_check_cb,
 572                },
 573        {
 574                .ident = "X360",
 575                .matches = {
 576                        DMI_MATCH(DMI_SYS_VENDOR,
 577                                        "SAMSUNG ELECTRONICS CO., LTD."),
 578                        DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
 579                        DMI_MATCH(DMI_BOARD_NAME, "X360"),
 580                },
 581                .callback = dmi_check_cb,
 582        },
 583        {
 584                .ident = "R410 Plus",
 585                .matches = {
 586                        DMI_MATCH(DMI_SYS_VENDOR,
 587                                        "SAMSUNG ELECTRONICS CO., LTD."),
 588                        DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
 589                        DMI_MATCH(DMI_BOARD_NAME, "R460"),
 590                },
 591                .callback = dmi_check_cb,
 592        },
 593        {
 594                .ident = "R518",
 595                .matches = {
 596                        DMI_MATCH(DMI_SYS_VENDOR,
 597                                        "SAMSUNG ELECTRONICS CO., LTD."),
 598                        DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
 599                        DMI_MATCH(DMI_BOARD_NAME, "R518"),
 600                },
 601                .callback = dmi_check_cb,
 602        },
 603        {
 604                .ident = "R519/R719",
 605                .matches = {
 606                        DMI_MATCH(DMI_SYS_VENDOR,
 607                                        "SAMSUNG ELECTRONICS CO., LTD."),
 608                        DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
 609                        DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
 610                },
 611                .callback = dmi_check_cb,
 612        },
 613        {
 614                .ident = "N150/N210/N220",
 615                .matches = {
 616                        DMI_MATCH(DMI_SYS_VENDOR,
 617                                        "SAMSUNG ELECTRONICS CO., LTD."),
 618                        DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
 619                        DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
 620                },
 621                .callback = dmi_check_cb,
 622        },
 623        {
 624                .ident = "N150/N210/N220/N230",
 625                .matches = {
 626                        DMI_MATCH(DMI_SYS_VENDOR,
 627                                        "SAMSUNG ELECTRONICS CO., LTD."),
 628                        DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
 629                        DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
 630                },
 631                .callback = dmi_check_cb,
 632        },
 633        {
 634                .ident = "N150P/N210P/N220P",
 635                .matches = {
 636                        DMI_MATCH(DMI_SYS_VENDOR,
 637                                        "SAMSUNG ELECTRONICS CO., LTD."),
 638                        DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
 639                        DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
 640                },
 641                .callback = dmi_check_cb,
 642        },
 643        {
 644                .ident = "R530/R730",
 645                .matches = {
 646                      DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
 647                      DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
 648                      DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
 649                },
 650                .callback = dmi_check_cb,
 651        },
 652        {
 653                .ident = "NF110/NF210/NF310",
 654                .matches = {
 655                        DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
 656                        DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
 657                        DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
 658                },
 659                .callback = dmi_check_cb,
 660        },
 661        {
 662                .ident = "N145P/N250P/N260P",
 663                .matches = {
 664                        DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
 665                        DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
 666                        DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
 667                },
 668                .callback = dmi_check_cb,
 669        },
 670        {
 671                .ident = "R70/R71",
 672                .matches = {
 673                        DMI_MATCH(DMI_SYS_VENDOR,
 674                                        "SAMSUNG ELECTRONICS CO., LTD."),
 675                        DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
 676                        DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
 677                },
 678                .callback = dmi_check_cb,
 679        },
 680        {
 681                .ident = "P460",
 682                .matches = {
 683                        DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
 684                        DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
 685                        DMI_MATCH(DMI_BOARD_NAME, "P460"),
 686                },
 687                .callback = dmi_check_cb,
 688        },
 689        { },
 690};
 691MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
 692
 693static int find_signature(void __iomem *memcheck, const char *testStr)
 694{
 695        int i = 0;
 696        int loca;
 697
 698        for (loca = 0; loca < 0xffff; loca++) {
 699                char temp = readb(memcheck + loca);
 700
 701                if (temp == testStr[i]) {
 702                        if (i == strlen(testStr)-1)
 703                                break;
 704                        ++i;
 705                } else {
 706                        i = 0;
 707                }
 708        }
 709        return loca;
 710}
 711
 712static int __init samsung_init(void)
 713{
 714        struct backlight_properties props;
 715        struct sabi_retval sretval;
 716        unsigned int ifaceP;
 717        int i;
 718        int loca;
 719        int retval;
 720
 721        mutex_init(&sabi_mutex);
 722
 723        if (!force && !dmi_check_system(samsung_dmi_table))
 724                return -ENODEV;
 725
 726        f0000_segment = ioremap_nocache(0xf0000, 0xffff);
 727        if (!f0000_segment) {
 728                pr_err("Can't map the segment at 0xf0000\n");
 729                return -EINVAL;
 730        }
 731
 732        /* Try to find one of the signatures in memory to find the header */
 733        for (i = 0; sabi_configs[i].test_string != 0; ++i) {
 734                sabi_config = &sabi_configs[i];
 735                loca = find_signature(f0000_segment, sabi_config->test_string);
 736                if (loca != 0xffff)
 737                        break;
 738        }
 739
 740        if (loca == 0xffff) {
 741                pr_err("This computer does not support SABI\n");
 742                goto error_no_signature;
 743        }
 744
 745        /* point to the SMI port Number */
 746        loca += 1;
 747        sabi = (f0000_segment + loca);
 748
 749        if (debug) {
 750                printk(KERN_DEBUG "This computer supports SABI==%x\n",
 751                        loca + 0xf0000 - 6);
 752                printk(KERN_DEBUG "SABI header:\n");
 753                printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
 754                        readw(sabi + sabi_config->header_offsets.port));
 755                printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
 756                        readb(sabi + sabi_config->header_offsets.iface_func));
 757                printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
 758                        readb(sabi + sabi_config->header_offsets.en_mem));
 759                printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
 760                        readb(sabi + sabi_config->header_offsets.re_mem));
 761                printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
 762                        readw(sabi + sabi_config->header_offsets.data_offset));
 763                printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
 764                        readw(sabi + sabi_config->header_offsets.data_segment));
 765        }
 766
 767        /* Get a pointer to the SABI Interface */
 768        ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4;
 769        ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff;
 770        sabi_iface = ioremap_nocache(ifaceP, 16);
 771        if (!sabi_iface) {
 772                pr_err("Can't remap %x\n", ifaceP);
 773                goto exit;
 774        }
 775        if (debug) {
 776                printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
 777                printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface);
 778
 779                test_backlight();
 780                test_wireless();
 781
 782                retval = sabi_get_command(sabi_config->commands.get_brightness,
 783                                          &sretval);
 784                printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
 785        }
 786
 787        /* Turn on "Linux" mode in the BIOS */
 788        if (sabi_config->commands.set_linux != 0xff) {
 789                retval = sabi_set_command(sabi_config->commands.set_linux,
 790                                          0x81);
 791                if (retval) {
 792                        pr_warn("Linux mode was not set!\n");
 793                        goto error_no_platform;
 794                }
 795        }
 796
 797        /* knock up a platform device to hang stuff off of */
 798        sdev = platform_device_register_simple("samsung", -1, NULL, 0);
 799        if (IS_ERR(sdev))
 800                goto error_no_platform;
 801
 802        /* create a backlight device to talk to this one */
 803        memset(&props, 0, sizeof(struct backlight_properties));
 804        props.type = BACKLIGHT_PLATFORM;
 805        props.max_brightness = sabi_config->max_brightness;
 806        backlight_device = backlight_device_register("samsung", &sdev->dev,
 807                                                     NULL, &backlight_ops,
 808                                                     &props);
 809        if (IS_ERR(backlight_device))
 810                goto error_no_backlight;
 811
 812        backlight_device->props.brightness = read_brightness();
 813        backlight_device->props.power = FB_BLANK_UNBLANK;
 814        backlight_update_status(backlight_device);
 815
 816        retval = init_wireless(sdev);
 817        if (retval)
 818                goto error_no_rfk;
 819
 820        retval = device_create_file(&sdev->dev, &dev_attr_performance_level);
 821        if (retval)
 822                goto error_file_create;
 823
 824exit:
 825        return 0;
 826
 827error_file_create:
 828        destroy_wireless();
 829
 830error_no_rfk:
 831        backlight_device_unregister(backlight_device);
 832
 833error_no_backlight:
 834        platform_device_unregister(sdev);
 835
 836error_no_platform:
 837        iounmap(sabi_iface);
 838
 839error_no_signature:
 840        iounmap(f0000_segment);
 841        return -EINVAL;
 842}
 843
 844static void __exit samsung_exit(void)
 845{
 846        /* Turn off "Linux" mode in the BIOS */
 847        if (sabi_config->commands.set_linux != 0xff)
 848                sabi_set_command(sabi_config->commands.set_linux, 0x80);
 849
 850        device_remove_file(&sdev->dev, &dev_attr_performance_level);
 851        backlight_device_unregister(backlight_device);
 852        destroy_wireless();
 853        iounmap(sabi_iface);
 854        iounmap(f0000_segment);
 855        platform_device_unregister(sdev);
 856}
 857
 858module_init(samsung_init);
 859module_exit(samsung_exit);
 860
 861MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
 862MODULE_DESCRIPTION("Samsung Backlight driver");
 863MODULE_LICENSE("GPL");
 864