Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Samsung Laptop driver
0004  *
0005  * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
0006  * Copyright (C) 2009,2011 Novell Inc.
0007  */
0008 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0009 
0010 #include <linux/kernel.h>
0011 #include <linux/init.h>
0012 #include <linux/module.h>
0013 #include <linux/delay.h>
0014 #include <linux/pci.h>
0015 #include <linux/backlight.h>
0016 #include <linux/leds.h>
0017 #include <linux/fb.h>
0018 #include <linux/dmi.h>
0019 #include <linux/platform_device.h>
0020 #include <linux/rfkill.h>
0021 #include <linux/acpi.h>
0022 #include <linux/seq_file.h>
0023 #include <linux/debugfs.h>
0024 #include <linux/ctype.h>
0025 #include <linux/efi.h>
0026 #include <linux/suspend.h>
0027 #include <acpi/video.h>
0028 
0029 /*
0030  * This driver is needed because a number of Samsung laptops do not hook
0031  * their control settings through ACPI.  So we have to poke around in the
0032  * BIOS to do things like brightness values, and "special" key controls.
0033  */
0034 
0035 /*
0036  * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
0037  * be reserved by the BIOS (which really doesn't make much sense), we tell
0038  * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
0039  */
0040 #define MAX_BRIGHT  0x07
0041 
0042 
0043 #define SABI_IFACE_MAIN         0x00
0044 #define SABI_IFACE_SUB          0x02
0045 #define SABI_IFACE_COMPLETE     0x04
0046 #define SABI_IFACE_DATA         0x05
0047 
0048 #define WL_STATUS_WLAN          0x0
0049 #define WL_STATUS_BT            0x2
0050 
0051 /* Structure get/set data using sabi */
0052 struct sabi_data {
0053     union {
0054         struct {
0055             u32 d0;
0056             u32 d1;
0057             u16 d2;
0058             u8  d3;
0059         };
0060         u8 data[11];
0061     };
0062 };
0063 
0064 struct sabi_header_offsets {
0065     u8 port;
0066     u8 re_mem;
0067     u8 iface_func;
0068     u8 en_mem;
0069     u8 data_offset;
0070     u8 data_segment;
0071 };
0072 
0073 struct sabi_commands {
0074     /*
0075      * Brightness is 0 - 8, as described above.
0076      * Value 0 is for the BIOS to use
0077      */
0078     u16 get_brightness;
0079     u16 set_brightness;
0080 
0081     /*
0082      * first byte:
0083      * 0x00 - wireless is off
0084      * 0x01 - wireless is on
0085      * second byte:
0086      * 0x02 - 3G is off
0087      * 0x03 - 3G is on
0088      * TODO, verify 3G is correct, that doesn't seem right...
0089      */
0090     u16 get_wireless_button;
0091     u16 set_wireless_button;
0092 
0093     /* 0 is off, 1 is on */
0094     u16 get_backlight;
0095     u16 set_backlight;
0096 
0097     /*
0098      * 0x80 or 0x00 - no action
0099      * 0x81 - recovery key pressed
0100      */
0101     u16 get_recovery_mode;
0102     u16 set_recovery_mode;
0103 
0104     /*
0105      * on seclinux: 0 is low, 1 is high,
0106      * on swsmi: 0 is normal, 1 is silent, 2 is turbo
0107      */
0108     u16 get_performance_level;
0109     u16 set_performance_level;
0110 
0111     /* 0x80 is off, 0x81 is on */
0112     u16 get_battery_life_extender;
0113     u16 set_battery_life_extender;
0114 
0115     /* 0x80 is off, 0x81 is on */
0116     u16 get_usb_charge;
0117     u16 set_usb_charge;
0118 
0119     /* the first byte is for bluetooth and the third one is for wlan */
0120     u16 get_wireless_status;
0121     u16 set_wireless_status;
0122 
0123     /* 0x80 is off, 0x81 is on */
0124     u16 get_lid_handling;
0125     u16 set_lid_handling;
0126 
0127     /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
0128     u16 kbd_backlight;
0129 
0130     /*
0131      * Tell the BIOS that Linux is running on this machine.
0132      * 81 is on, 80 is off
0133      */
0134     u16 set_linux;
0135 };
0136 
0137 struct sabi_performance_level {
0138     const char *name;
0139     u16 value;
0140 };
0141 
0142 struct sabi_config {
0143     int sabi_version;
0144     const char *test_string;
0145     u16 main_function;
0146     const struct sabi_header_offsets header_offsets;
0147     const struct sabi_commands commands;
0148     const struct sabi_performance_level performance_levels[4];
0149     u8 min_brightness;
0150     u8 max_brightness;
0151 };
0152 
0153 static const struct sabi_config sabi_configs[] = {
0154     {
0155         /* I don't know if it is really 2, but it is
0156          * less than 3 anyway */
0157         .sabi_version = 2,
0158 
0159         .test_string = "SECLINUX",
0160 
0161         .main_function = 0x4c49,
0162 
0163         .header_offsets = {
0164             .port = 0x00,
0165             .re_mem = 0x02,
0166             .iface_func = 0x03,
0167             .en_mem = 0x04,
0168             .data_offset = 0x05,
0169             .data_segment = 0x07,
0170         },
0171 
0172         .commands = {
0173             .get_brightness = 0x00,
0174             .set_brightness = 0x01,
0175 
0176             .get_wireless_button = 0x02,
0177             .set_wireless_button = 0x03,
0178 
0179             .get_backlight = 0x04,
0180             .set_backlight = 0x05,
0181 
0182             .get_recovery_mode = 0x06,
0183             .set_recovery_mode = 0x07,
0184 
0185             .get_performance_level = 0x08,
0186             .set_performance_level = 0x09,
0187 
0188             .get_battery_life_extender = 0xFFFF,
0189             .set_battery_life_extender = 0xFFFF,
0190 
0191             .get_usb_charge = 0xFFFF,
0192             .set_usb_charge = 0xFFFF,
0193 
0194             .get_wireless_status = 0xFFFF,
0195             .set_wireless_status = 0xFFFF,
0196 
0197             .get_lid_handling = 0xFFFF,
0198             .set_lid_handling = 0xFFFF,
0199 
0200             .kbd_backlight = 0xFFFF,
0201 
0202             .set_linux = 0x0a,
0203         },
0204 
0205         .performance_levels = {
0206             {
0207                 .name = "silent",
0208                 .value = 0,
0209             },
0210             {
0211                 .name = "normal",
0212                 .value = 1,
0213             },
0214             { },
0215         },
0216         .min_brightness = 1,
0217         .max_brightness = 8,
0218     },
0219     {
0220         .sabi_version = 3,
0221 
0222         .test_string = "SwSmi@",
0223 
0224         .main_function = 0x5843,
0225 
0226         .header_offsets = {
0227             .port = 0x00,
0228             .re_mem = 0x04,
0229             .iface_func = 0x02,
0230             .en_mem = 0x03,
0231             .data_offset = 0x05,
0232             .data_segment = 0x07,
0233         },
0234 
0235         .commands = {
0236             .get_brightness = 0x10,
0237             .set_brightness = 0x11,
0238 
0239             .get_wireless_button = 0x12,
0240             .set_wireless_button = 0x13,
0241 
0242             .get_backlight = 0x2d,
0243             .set_backlight = 0x2e,
0244 
0245             .get_recovery_mode = 0xff,
0246             .set_recovery_mode = 0xff,
0247 
0248             .get_performance_level = 0x31,
0249             .set_performance_level = 0x32,
0250 
0251             .get_battery_life_extender = 0x65,
0252             .set_battery_life_extender = 0x66,
0253 
0254             .get_usb_charge = 0x67,
0255             .set_usb_charge = 0x68,
0256 
0257             .get_wireless_status = 0x69,
0258             .set_wireless_status = 0x6a,
0259 
0260             .get_lid_handling = 0x6d,
0261             .set_lid_handling = 0x6e,
0262 
0263             .kbd_backlight = 0x78,
0264 
0265             .set_linux = 0xff,
0266         },
0267 
0268         .performance_levels = {
0269             {
0270                 .name = "normal",
0271                 .value = 0,
0272             },
0273             {
0274                 .name = "silent",
0275                 .value = 1,
0276             },
0277             {
0278                 .name = "overclock",
0279                 .value = 2,
0280             },
0281             { },
0282         },
0283         .min_brightness = 0,
0284         .max_brightness = 8,
0285     },
0286     { },
0287 };
0288 
0289 /*
0290  * samsung-laptop/    - debugfs root directory
0291  *   f0000_segment    - dump f0000 segment
0292  *   command          - current command
0293  *   data             - current data
0294  *   d0, d1, d2, d3   - data fields
0295  *   call             - call SABI using command and data
0296  *
0297  * This allow to call arbitrary sabi commands wihout
0298  * modifying the driver at all.
0299  * For example, setting the keyboard backlight brightness to 5
0300  *
0301  *  echo 0x78 > command
0302  *  echo 0x0582 > d0
0303  *  echo 0 > d1
0304  *  echo 0 > d2
0305  *  echo 0 > d3
0306  *  cat call
0307  */
0308 
0309 struct samsung_laptop_debug {
0310     struct dentry *root;
0311     struct sabi_data data;
0312     u16 command;
0313 
0314     struct debugfs_blob_wrapper f0000_wrapper;
0315     struct debugfs_blob_wrapper data_wrapper;
0316     struct debugfs_blob_wrapper sdiag_wrapper;
0317 };
0318 
0319 struct samsung_laptop;
0320 
0321 struct samsung_rfkill {
0322     struct samsung_laptop *samsung;
0323     struct rfkill *rfkill;
0324     enum rfkill_type type;
0325 };
0326 
0327 struct samsung_laptop {
0328     const struct sabi_config *config;
0329 
0330     void __iomem *sabi;
0331     void __iomem *sabi_iface;
0332     void __iomem *f0000_segment;
0333 
0334     struct mutex sabi_mutex;
0335 
0336     struct platform_device *platform_device;
0337     struct backlight_device *backlight_device;
0338 
0339     struct samsung_rfkill wlan;
0340     struct samsung_rfkill bluetooth;
0341 
0342     struct led_classdev kbd_led;
0343     int kbd_led_wk;
0344     struct workqueue_struct *led_workqueue;
0345     struct work_struct kbd_led_work;
0346 
0347     struct samsung_laptop_debug debug;
0348     struct samsung_quirks *quirks;
0349 
0350     struct notifier_block pm_nb;
0351 
0352     bool handle_backlight;
0353     bool has_stepping_quirk;
0354 
0355     char sdiag[64];
0356 };
0357 
0358 struct samsung_quirks {
0359     bool broken_acpi_video;
0360     bool four_kbd_backlight_levels;
0361     bool enable_kbd_backlight;
0362     bool use_native_backlight;
0363     bool lid_handling;
0364 };
0365 
0366 static struct samsung_quirks samsung_unknown = {};
0367 
0368 static struct samsung_quirks samsung_broken_acpi_video = {
0369     .broken_acpi_video = true,
0370 };
0371 
0372 static struct samsung_quirks samsung_use_native_backlight = {
0373     .use_native_backlight = true,
0374 };
0375 
0376 static struct samsung_quirks samsung_np740u3e = {
0377     .four_kbd_backlight_levels = true,
0378     .enable_kbd_backlight = true,
0379 };
0380 
0381 static struct samsung_quirks samsung_lid_handling = {
0382     .lid_handling = true,
0383 };
0384 
0385 static bool force;
0386 module_param(force, bool, 0);
0387 MODULE_PARM_DESC(force,
0388         "Disable the DMI check and forces the driver to be loaded");
0389 
0390 static bool debug;
0391 module_param(debug, bool, 0644);
0392 MODULE_PARM_DESC(debug, "Debug enabled or not");
0393 
0394 static int sabi_command(struct samsung_laptop *samsung, u16 command,
0395             struct sabi_data *in,
0396             struct sabi_data *out)
0397 {
0398     const struct sabi_config *config = samsung->config;
0399     int ret = 0;
0400     u16 port = readw(samsung->sabi + config->header_offsets.port);
0401     u8 complete, iface_data;
0402 
0403     mutex_lock(&samsung->sabi_mutex);
0404 
0405     if (debug) {
0406         if (in)
0407             pr_info("SABI command:0x%04x "
0408                 "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
0409                 command, in->d0, in->d1, in->d2, in->d3);
0410         else
0411             pr_info("SABI command:0x%04x", command);
0412     }
0413 
0414     /* enable memory to be able to write to it */
0415     outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
0416 
0417     /* write out the command */
0418     writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
0419     writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
0420     writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
0421     if (in) {
0422         writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
0423         writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
0424         writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
0425         writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
0426     }
0427     outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
0428 
0429     /* write protect memory to make it safe */
0430     outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
0431 
0432     /* see if the command actually succeeded */
0433     complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
0434     iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
0435 
0436     /* iface_data = 0xFF happens when a command is not known
0437      * so we only add a warning in debug mode since we will
0438      * probably issue some unknown command at startup to find
0439      * out which features are supported */
0440     if (complete != 0xaa || (iface_data == 0xff && debug))
0441         pr_warn("SABI command 0x%04x failed with"
0442             " completion flag 0x%02x and interface data 0x%02x",
0443             command, complete, iface_data);
0444 
0445     if (complete != 0xaa || iface_data == 0xff) {
0446         ret = -EINVAL;
0447         goto exit;
0448     }
0449 
0450     if (out) {
0451         out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
0452         out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
0453         out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
0454         out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
0455     }
0456 
0457     if (debug && out) {
0458         pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
0459             out->d0, out->d1, out->d2, out->d3);
0460     }
0461 
0462 exit:
0463     mutex_unlock(&samsung->sabi_mutex);
0464     return ret;
0465 }
0466 
0467 /* simple wrappers usable with most commands */
0468 static int sabi_set_commandb(struct samsung_laptop *samsung,
0469                  u16 command, u8 data)
0470 {
0471     struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
0472 
0473     in.data[0] = data;
0474     return sabi_command(samsung, command, &in, NULL);
0475 }
0476 
0477 static int read_brightness(struct samsung_laptop *samsung)
0478 {
0479     const struct sabi_config *config = samsung->config;
0480     const struct sabi_commands *commands = &samsung->config->commands;
0481     struct sabi_data sretval;
0482     int user_brightness = 0;
0483     int retval;
0484 
0485     retval = sabi_command(samsung, commands->get_brightness,
0486                   NULL, &sretval);
0487     if (retval)
0488         return retval;
0489 
0490     user_brightness = sretval.data[0];
0491     if (user_brightness > config->min_brightness)
0492         user_brightness -= config->min_brightness;
0493     else
0494         user_brightness = 0;
0495 
0496     return user_brightness;
0497 }
0498 
0499 static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
0500 {
0501     const struct sabi_config *config = samsung->config;
0502     const struct sabi_commands *commands = &samsung->config->commands;
0503     u8 user_level = user_brightness + config->min_brightness;
0504 
0505     if (samsung->has_stepping_quirk && user_level != 0) {
0506         /*
0507          * short circuit if the specified level is what's already set
0508          * to prevent the screen from flickering needlessly
0509          */
0510         if (user_brightness == read_brightness(samsung))
0511             return;
0512 
0513         sabi_set_commandb(samsung, commands->set_brightness, 0);
0514     }
0515 
0516     sabi_set_commandb(samsung, commands->set_brightness, user_level);
0517 }
0518 
0519 static int get_brightness(struct backlight_device *bd)
0520 {
0521     struct samsung_laptop *samsung = bl_get_data(bd);
0522 
0523     return read_brightness(samsung);
0524 }
0525 
0526 static void check_for_stepping_quirk(struct samsung_laptop *samsung)
0527 {
0528     int initial_level;
0529     int check_level;
0530     int orig_level = read_brightness(samsung);
0531 
0532     /*
0533      * Some laptops exhibit the strange behaviour of stepping toward
0534      * (rather than setting) the brightness except when changing to/from
0535      * brightness level 0. This behaviour is checked for here and worked
0536      * around in set_brightness.
0537      */
0538 
0539     if (orig_level == 0)
0540         set_brightness(samsung, 1);
0541 
0542     initial_level = read_brightness(samsung);
0543 
0544     if (initial_level <= 2)
0545         check_level = initial_level + 2;
0546     else
0547         check_level = initial_level - 2;
0548 
0549     samsung->has_stepping_quirk = false;
0550     set_brightness(samsung, check_level);
0551 
0552     if (read_brightness(samsung) != check_level) {
0553         samsung->has_stepping_quirk = true;
0554         pr_info("enabled workaround for brightness stepping quirk\n");
0555     }
0556 
0557     set_brightness(samsung, orig_level);
0558 }
0559 
0560 static int update_status(struct backlight_device *bd)
0561 {
0562     struct samsung_laptop *samsung = bl_get_data(bd);
0563     const struct sabi_commands *commands = &samsung->config->commands;
0564 
0565     set_brightness(samsung, bd->props.brightness);
0566 
0567     if (bd->props.power == FB_BLANK_UNBLANK)
0568         sabi_set_commandb(samsung, commands->set_backlight, 1);
0569     else
0570         sabi_set_commandb(samsung, commands->set_backlight, 0);
0571 
0572     return 0;
0573 }
0574 
0575 static const struct backlight_ops backlight_ops = {
0576     .get_brightness = get_brightness,
0577     .update_status  = update_status,
0578 };
0579 
0580 static int seclinux_rfkill_set(void *data, bool blocked)
0581 {
0582     struct samsung_rfkill *srfkill = data;
0583     struct samsung_laptop *samsung = srfkill->samsung;
0584     const struct sabi_commands *commands = &samsung->config->commands;
0585 
0586     return sabi_set_commandb(samsung, commands->set_wireless_button,
0587                  !blocked);
0588 }
0589 
0590 static const struct rfkill_ops seclinux_rfkill_ops = {
0591     .set_block = seclinux_rfkill_set,
0592 };
0593 
0594 static int swsmi_wireless_status(struct samsung_laptop *samsung,
0595                  struct sabi_data *data)
0596 {
0597     const struct sabi_commands *commands = &samsung->config->commands;
0598 
0599     return sabi_command(samsung, commands->get_wireless_status,
0600                 NULL, data);
0601 }
0602 
0603 static int swsmi_rfkill_set(void *priv, bool blocked)
0604 {
0605     struct samsung_rfkill *srfkill = priv;
0606     struct samsung_laptop *samsung = srfkill->samsung;
0607     const struct sabi_commands *commands = &samsung->config->commands;
0608     struct sabi_data data;
0609     int ret, i;
0610 
0611     ret = swsmi_wireless_status(samsung, &data);
0612     if (ret)
0613         return ret;
0614 
0615     /* Don't set the state for non-present devices */
0616     for (i = 0; i < 4; i++)
0617         if (data.data[i] == 0x02)
0618             data.data[1] = 0;
0619 
0620     if (srfkill->type == RFKILL_TYPE_WLAN)
0621         data.data[WL_STATUS_WLAN] = !blocked;
0622     else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
0623         data.data[WL_STATUS_BT] = !blocked;
0624 
0625     return sabi_command(samsung, commands->set_wireless_status,
0626                 &data, &data);
0627 }
0628 
0629 static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
0630 {
0631     struct samsung_rfkill *srfkill = priv;
0632     struct samsung_laptop *samsung = srfkill->samsung;
0633     struct sabi_data data;
0634     int ret;
0635 
0636     ret = swsmi_wireless_status(samsung, &data);
0637     if (ret)
0638         return ;
0639 
0640     if (srfkill->type == RFKILL_TYPE_WLAN)
0641         ret = data.data[WL_STATUS_WLAN];
0642     else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
0643         ret = data.data[WL_STATUS_BT];
0644     else
0645         return ;
0646 
0647     rfkill_set_sw_state(rfkill, !ret);
0648 }
0649 
0650 static const struct rfkill_ops swsmi_rfkill_ops = {
0651     .set_block = swsmi_rfkill_set,
0652     .query = swsmi_rfkill_query,
0653 };
0654 
0655 static ssize_t get_performance_level(struct device *dev,
0656                      struct device_attribute *attr, char *buf)
0657 {
0658     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0659     const struct sabi_config *config = samsung->config;
0660     const struct sabi_commands *commands = &config->commands;
0661     struct sabi_data sretval;
0662     int retval;
0663     int i;
0664 
0665     /* Read the state */
0666     retval = sabi_command(samsung, commands->get_performance_level,
0667                   NULL, &sretval);
0668     if (retval)
0669         return retval;
0670 
0671     /* The logic is backwards, yeah, lots of fun... */
0672     for (i = 0; config->performance_levels[i].name; ++i) {
0673         if (sretval.data[0] == config->performance_levels[i].value)
0674             return sprintf(buf, "%s\n", config->performance_levels[i].name);
0675     }
0676     return sprintf(buf, "%s\n", "unknown");
0677 }
0678 
0679 static ssize_t set_performance_level(struct device *dev,
0680                 struct device_attribute *attr, const char *buf,
0681                 size_t count)
0682 {
0683     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0684     const struct sabi_config *config = samsung->config;
0685     const struct sabi_commands *commands = &config->commands;
0686     int i;
0687 
0688     if (count < 1)
0689         return count;
0690 
0691     for (i = 0; config->performance_levels[i].name; ++i) {
0692         const struct sabi_performance_level *level =
0693             &config->performance_levels[i];
0694         if (!strncasecmp(level->name, buf, strlen(level->name))) {
0695             sabi_set_commandb(samsung,
0696                       commands->set_performance_level,
0697                       level->value);
0698             break;
0699         }
0700     }
0701 
0702     if (!config->performance_levels[i].name)
0703         return -EINVAL;
0704 
0705     return count;
0706 }
0707 
0708 static DEVICE_ATTR(performance_level, 0644,
0709            get_performance_level, set_performance_level);
0710 
0711 static int read_battery_life_extender(struct samsung_laptop *samsung)
0712 {
0713     const struct sabi_commands *commands = &samsung->config->commands;
0714     struct sabi_data data;
0715     int retval;
0716 
0717     if (commands->get_battery_life_extender == 0xFFFF)
0718         return -ENODEV;
0719 
0720     memset(&data, 0, sizeof(data));
0721     data.data[0] = 0x80;
0722     retval = sabi_command(samsung, commands->get_battery_life_extender,
0723                   &data, &data);
0724 
0725     if (retval)
0726         return retval;
0727 
0728     if (data.data[0] != 0 && data.data[0] != 1)
0729         return -ENODEV;
0730 
0731     return data.data[0];
0732 }
0733 
0734 static int write_battery_life_extender(struct samsung_laptop *samsung,
0735                        int enabled)
0736 {
0737     const struct sabi_commands *commands = &samsung->config->commands;
0738     struct sabi_data data;
0739 
0740     memset(&data, 0, sizeof(data));
0741     data.data[0] = 0x80 | enabled;
0742     return sabi_command(samsung, commands->set_battery_life_extender,
0743                 &data, NULL);
0744 }
0745 
0746 static ssize_t get_battery_life_extender(struct device *dev,
0747                      struct device_attribute *attr,
0748                      char *buf)
0749 {
0750     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0751     int ret;
0752 
0753     ret = read_battery_life_extender(samsung);
0754     if (ret < 0)
0755         return ret;
0756 
0757     return sprintf(buf, "%d\n", ret);
0758 }
0759 
0760 static ssize_t set_battery_life_extender(struct device *dev,
0761                     struct device_attribute *attr,
0762                     const char *buf, size_t count)
0763 {
0764     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0765     int ret, value;
0766 
0767     if (!count || kstrtoint(buf, 0, &value) != 0)
0768         return -EINVAL;
0769 
0770     ret = write_battery_life_extender(samsung, !!value);
0771     if (ret < 0)
0772         return ret;
0773 
0774     return count;
0775 }
0776 
0777 static DEVICE_ATTR(battery_life_extender, 0644,
0778            get_battery_life_extender, set_battery_life_extender);
0779 
0780 static int read_usb_charge(struct samsung_laptop *samsung)
0781 {
0782     const struct sabi_commands *commands = &samsung->config->commands;
0783     struct sabi_data data;
0784     int retval;
0785 
0786     if (commands->get_usb_charge == 0xFFFF)
0787         return -ENODEV;
0788 
0789     memset(&data, 0, sizeof(data));
0790     data.data[0] = 0x80;
0791     retval = sabi_command(samsung, commands->get_usb_charge,
0792                   &data, &data);
0793 
0794     if (retval)
0795         return retval;
0796 
0797     if (data.data[0] != 0 && data.data[0] != 1)
0798         return -ENODEV;
0799 
0800     return data.data[0];
0801 }
0802 
0803 static int write_usb_charge(struct samsung_laptop *samsung,
0804                 int enabled)
0805 {
0806     const struct sabi_commands *commands = &samsung->config->commands;
0807     struct sabi_data data;
0808 
0809     memset(&data, 0, sizeof(data));
0810     data.data[0] = 0x80 | enabled;
0811     return sabi_command(samsung, commands->set_usb_charge,
0812                 &data, NULL);
0813 }
0814 
0815 static ssize_t get_usb_charge(struct device *dev,
0816                   struct device_attribute *attr,
0817                   char *buf)
0818 {
0819     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0820     int ret;
0821 
0822     ret = read_usb_charge(samsung);
0823     if (ret < 0)
0824         return ret;
0825 
0826     return sprintf(buf, "%d\n", ret);
0827 }
0828 
0829 static ssize_t set_usb_charge(struct device *dev,
0830                   struct device_attribute *attr,
0831                   const char *buf, size_t count)
0832 {
0833     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0834     int ret, value;
0835 
0836     if (!count || kstrtoint(buf, 0, &value) != 0)
0837         return -EINVAL;
0838 
0839     ret = write_usb_charge(samsung, !!value);
0840     if (ret < 0)
0841         return ret;
0842 
0843     return count;
0844 }
0845 
0846 static DEVICE_ATTR(usb_charge, 0644,
0847            get_usb_charge, set_usb_charge);
0848 
0849 static int read_lid_handling(struct samsung_laptop *samsung)
0850 {
0851     const struct sabi_commands *commands = &samsung->config->commands;
0852     struct sabi_data data;
0853     int retval;
0854 
0855     if (commands->get_lid_handling == 0xFFFF)
0856         return -ENODEV;
0857 
0858     memset(&data, 0, sizeof(data));
0859     retval = sabi_command(samsung, commands->get_lid_handling,
0860                   &data, &data);
0861 
0862     if (retval)
0863         return retval;
0864 
0865     return data.data[0] & 0x1;
0866 }
0867 
0868 static int write_lid_handling(struct samsung_laptop *samsung,
0869                   int enabled)
0870 {
0871     const struct sabi_commands *commands = &samsung->config->commands;
0872     struct sabi_data data;
0873 
0874     memset(&data, 0, sizeof(data));
0875     data.data[0] = 0x80 | enabled;
0876     return sabi_command(samsung, commands->set_lid_handling,
0877                 &data, NULL);
0878 }
0879 
0880 static ssize_t get_lid_handling(struct device *dev,
0881                 struct device_attribute *attr,
0882                 char *buf)
0883 {
0884     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0885     int ret;
0886 
0887     ret = read_lid_handling(samsung);
0888     if (ret < 0)
0889         return ret;
0890 
0891     return sprintf(buf, "%d\n", ret);
0892 }
0893 
0894 static ssize_t set_lid_handling(struct device *dev,
0895                 struct device_attribute *attr,
0896                 const char *buf, size_t count)
0897 {
0898     struct samsung_laptop *samsung = dev_get_drvdata(dev);
0899     int ret, value;
0900 
0901     if (!count || kstrtoint(buf, 0, &value) != 0)
0902         return -EINVAL;
0903 
0904     ret = write_lid_handling(samsung, !!value);
0905     if (ret < 0)
0906         return ret;
0907 
0908     return count;
0909 }
0910 
0911 static DEVICE_ATTR(lid_handling, 0644,
0912            get_lid_handling, set_lid_handling);
0913 
0914 static struct attribute *platform_attributes[] = {
0915     &dev_attr_performance_level.attr,
0916     &dev_attr_battery_life_extender.attr,
0917     &dev_attr_usb_charge.attr,
0918     &dev_attr_lid_handling.attr,
0919     NULL
0920 };
0921 
0922 static int find_signature(void __iomem *memcheck, const char *testStr)
0923 {
0924     int i = 0;
0925     int loca;
0926 
0927     for (loca = 0; loca < 0xffff; loca++) {
0928         char temp = readb(memcheck + loca);
0929 
0930         if (temp == testStr[i]) {
0931             if (i == strlen(testStr)-1)
0932                 break;
0933             ++i;
0934         } else {
0935             i = 0;
0936         }
0937     }
0938     return loca;
0939 }
0940 
0941 static void samsung_rfkill_exit(struct samsung_laptop *samsung)
0942 {
0943     if (samsung->wlan.rfkill) {
0944         rfkill_unregister(samsung->wlan.rfkill);
0945         rfkill_destroy(samsung->wlan.rfkill);
0946         samsung->wlan.rfkill = NULL;
0947     }
0948     if (samsung->bluetooth.rfkill) {
0949         rfkill_unregister(samsung->bluetooth.rfkill);
0950         rfkill_destroy(samsung->bluetooth.rfkill);
0951         samsung->bluetooth.rfkill = NULL;
0952     }
0953 }
0954 
0955 static int samsung_new_rfkill(struct samsung_laptop *samsung,
0956                   struct samsung_rfkill *arfkill,
0957                   const char *name, enum rfkill_type type,
0958                   const struct rfkill_ops *ops,
0959                   int blocked)
0960 {
0961     struct rfkill **rfkill = &arfkill->rfkill;
0962     int ret;
0963 
0964     arfkill->type = type;
0965     arfkill->samsung = samsung;
0966 
0967     *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
0968                    type, ops, arfkill);
0969 
0970     if (!*rfkill)
0971         return -EINVAL;
0972 
0973     if (blocked != -1)
0974         rfkill_init_sw_state(*rfkill, blocked);
0975 
0976     ret = rfkill_register(*rfkill);
0977     if (ret) {
0978         rfkill_destroy(*rfkill);
0979         *rfkill = NULL;
0980         return ret;
0981     }
0982     return 0;
0983 }
0984 
0985 static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
0986 {
0987     return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
0988                   RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
0989 }
0990 
0991 static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
0992 {
0993     struct sabi_data data;
0994     int ret;
0995 
0996     ret = swsmi_wireless_status(samsung, &data);
0997     if (ret) {
0998         /* Some swsmi laptops use the old seclinux way to control
0999          * wireless devices */
1000         if (ret == -EINVAL)
1001             ret = samsung_rfkill_init_seclinux(samsung);
1002         return ret;
1003     }
1004 
1005     /* 0x02 seems to mean that the device is no present/available */
1006 
1007     if (data.data[WL_STATUS_WLAN] != 0x02)
1008         ret = samsung_new_rfkill(samsung, &samsung->wlan,
1009                      "samsung-wlan",
1010                      RFKILL_TYPE_WLAN,
1011                      &swsmi_rfkill_ops,
1012                      !data.data[WL_STATUS_WLAN]);
1013     if (ret)
1014         goto exit;
1015 
1016     if (data.data[WL_STATUS_BT] != 0x02)
1017         ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
1018                      "samsung-bluetooth",
1019                      RFKILL_TYPE_BLUETOOTH,
1020                      &swsmi_rfkill_ops,
1021                      !data.data[WL_STATUS_BT]);
1022     if (ret)
1023         goto exit;
1024 
1025 exit:
1026     if (ret)
1027         samsung_rfkill_exit(samsung);
1028 
1029     return ret;
1030 }
1031 
1032 static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
1033 {
1034     if (samsung->config->sabi_version == 2)
1035         return samsung_rfkill_init_seclinux(samsung);
1036     if (samsung->config->sabi_version == 3)
1037         return samsung_rfkill_init_swsmi(samsung);
1038     return 0;
1039 }
1040 
1041 static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
1042 {
1043     if (samsung->quirks->lid_handling)
1044         write_lid_handling(samsung, 0);
1045 }
1046 
1047 static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
1048 {
1049     int retval = 0;
1050 
1051     if (samsung->quirks->lid_handling)
1052         retval = write_lid_handling(samsung, 1);
1053 
1054     return retval;
1055 }
1056 
1057 static int kbd_backlight_enable(struct samsung_laptop *samsung)
1058 {
1059     const struct sabi_commands *commands = &samsung->config->commands;
1060     struct sabi_data data;
1061     int retval;
1062 
1063     if (commands->kbd_backlight == 0xFFFF)
1064         return -ENODEV;
1065 
1066     memset(&data, 0, sizeof(data));
1067     data.d0 = 0xaabb;
1068     retval = sabi_command(samsung, commands->kbd_backlight,
1069                   &data, &data);
1070 
1071     if (retval)
1072         return retval;
1073 
1074     if (data.d0 != 0xccdd)
1075         return -ENODEV;
1076     return 0;
1077 }
1078 
1079 static int kbd_backlight_read(struct samsung_laptop *samsung)
1080 {
1081     const struct sabi_commands *commands = &samsung->config->commands;
1082     struct sabi_data data;
1083     int retval;
1084 
1085     memset(&data, 0, sizeof(data));
1086     data.data[0] = 0x81;
1087     retval = sabi_command(samsung, commands->kbd_backlight,
1088                   &data, &data);
1089 
1090     if (retval)
1091         return retval;
1092 
1093     return data.data[0];
1094 }
1095 
1096 static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1097 {
1098     const struct sabi_commands *commands = &samsung->config->commands;
1099     struct sabi_data data;
1100 
1101     memset(&data, 0, sizeof(data));
1102     data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1103     return sabi_command(samsung, commands->kbd_backlight,
1104                 &data, NULL);
1105 }
1106 
1107 static void kbd_led_update(struct work_struct *work)
1108 {
1109     struct samsung_laptop *samsung;
1110 
1111     samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1112     kbd_backlight_write(samsung, samsung->kbd_led_wk);
1113 }
1114 
1115 static void kbd_led_set(struct led_classdev *led_cdev,
1116             enum led_brightness value)
1117 {
1118     struct samsung_laptop *samsung;
1119 
1120     samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1121 
1122     if (value > samsung->kbd_led.max_brightness)
1123         value = samsung->kbd_led.max_brightness;
1124 
1125     samsung->kbd_led_wk = value;
1126     queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1127 }
1128 
1129 static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1130 {
1131     struct samsung_laptop *samsung;
1132 
1133     samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1134     return kbd_backlight_read(samsung);
1135 }
1136 
1137 static void samsung_leds_exit(struct samsung_laptop *samsung)
1138 {
1139     led_classdev_unregister(&samsung->kbd_led);
1140     if (samsung->led_workqueue)
1141         destroy_workqueue(samsung->led_workqueue);
1142 }
1143 
1144 static int __init samsung_leds_init(struct samsung_laptop *samsung)
1145 {
1146     int ret = 0;
1147 
1148     samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1149     if (!samsung->led_workqueue)
1150         return -ENOMEM;
1151 
1152     if (kbd_backlight_enable(samsung) >= 0) {
1153         INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1154 
1155         samsung->kbd_led.name = "samsung::kbd_backlight";
1156         samsung->kbd_led.brightness_set = kbd_led_set;
1157         samsung->kbd_led.brightness_get = kbd_led_get;
1158         samsung->kbd_led.max_brightness = 8;
1159         if (samsung->quirks->four_kbd_backlight_levels)
1160             samsung->kbd_led.max_brightness = 4;
1161 
1162         ret = led_classdev_register(&samsung->platform_device->dev,
1163                        &samsung->kbd_led);
1164     }
1165 
1166     if (ret)
1167         samsung_leds_exit(samsung);
1168 
1169     return ret;
1170 }
1171 
1172 static void samsung_backlight_exit(struct samsung_laptop *samsung)
1173 {
1174     if (samsung->backlight_device) {
1175         backlight_device_unregister(samsung->backlight_device);
1176         samsung->backlight_device = NULL;
1177     }
1178 }
1179 
1180 static int __init samsung_backlight_init(struct samsung_laptop *samsung)
1181 {
1182     struct backlight_device *bd;
1183     struct backlight_properties props;
1184 
1185     if (!samsung->handle_backlight)
1186         return 0;
1187 
1188     memset(&props, 0, sizeof(struct backlight_properties));
1189     props.type = BACKLIGHT_PLATFORM;
1190     props.max_brightness = samsung->config->max_brightness -
1191         samsung->config->min_brightness;
1192 
1193     bd = backlight_device_register("samsung",
1194                        &samsung->platform_device->dev,
1195                        samsung, &backlight_ops,
1196                        &props);
1197     if (IS_ERR(bd))
1198         return PTR_ERR(bd);
1199 
1200     samsung->backlight_device = bd;
1201     samsung->backlight_device->props.brightness = read_brightness(samsung);
1202     samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
1203     backlight_update_status(samsung->backlight_device);
1204 
1205     return 0;
1206 }
1207 
1208 static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1209                     struct attribute *attr, int idx)
1210 {
1211     struct device *dev = kobj_to_dev(kobj);
1212     struct samsung_laptop *samsung = dev_get_drvdata(dev);
1213     bool ok = true;
1214 
1215     if (attr == &dev_attr_performance_level.attr)
1216         ok = !!samsung->config->performance_levels[0].name;
1217     if (attr == &dev_attr_battery_life_extender.attr)
1218         ok = !!(read_battery_life_extender(samsung) >= 0);
1219     if (attr == &dev_attr_usb_charge.attr)
1220         ok = !!(read_usb_charge(samsung) >= 0);
1221     if (attr == &dev_attr_lid_handling.attr)
1222         ok = !!(read_lid_handling(samsung) >= 0);
1223 
1224     return ok ? attr->mode : 0;
1225 }
1226 
1227 static const struct attribute_group platform_attribute_group = {
1228     .is_visible = samsung_sysfs_is_visible,
1229     .attrs = platform_attributes
1230 };
1231 
1232 static void samsung_sysfs_exit(struct samsung_laptop *samsung)
1233 {
1234     struct platform_device *device = samsung->platform_device;
1235 
1236     sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
1237 }
1238 
1239 static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
1240 {
1241     struct platform_device *device = samsung->platform_device;
1242 
1243     return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1244 
1245 }
1246 
1247 static int samsung_laptop_call_show(struct seq_file *m, void *data)
1248 {
1249     struct samsung_laptop *samsung = m->private;
1250     struct sabi_data *sdata = &samsung->debug.data;
1251     int ret;
1252 
1253     seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1254            samsung->debug.command,
1255            sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1256 
1257     ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
1258 
1259     if (ret) {
1260         seq_printf(m, "SABI command 0x%04x failed\n",
1261                samsung->debug.command);
1262         return ret;
1263     }
1264 
1265     seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1266            sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1267     return 0;
1268 }
1269 DEFINE_SHOW_ATTRIBUTE(samsung_laptop_call);
1270 
1271 static void samsung_debugfs_exit(struct samsung_laptop *samsung)
1272 {
1273     debugfs_remove_recursive(samsung->debug.root);
1274 }
1275 
1276 static void samsung_debugfs_init(struct samsung_laptop *samsung)
1277 {
1278     struct dentry *root;
1279 
1280     root = debugfs_create_dir("samsung-laptop", NULL);
1281     samsung->debug.root = root;
1282 
1283     samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
1284     samsung->debug.f0000_wrapper.size = 0xffff;
1285 
1286     samsung->debug.data_wrapper.data = &samsung->debug.data;
1287     samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
1288 
1289     samsung->debug.sdiag_wrapper.data = samsung->sdiag;
1290     samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
1291 
1292     debugfs_create_u16("command", 0644, root, &samsung->debug.command);
1293     debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0);
1294     debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1);
1295     debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2);
1296     debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3);
1297     debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper);
1298     debugfs_create_blob("f0000_segment", 0400, root,
1299                 &samsung->debug.f0000_wrapper);
1300     debugfs_create_file("call", 0444, root, samsung,
1301                 &samsung_laptop_call_fops);
1302     debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper);
1303 }
1304 
1305 static void samsung_sabi_exit(struct samsung_laptop *samsung)
1306 {
1307     const struct sabi_config *config = samsung->config;
1308 
1309     /* Turn off "Linux" mode in the BIOS */
1310     if (config && config->commands.set_linux != 0xff)
1311         sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
1312 
1313     if (samsung->sabi_iface) {
1314         iounmap(samsung->sabi_iface);
1315         samsung->sabi_iface = NULL;
1316     }
1317     if (samsung->f0000_segment) {
1318         iounmap(samsung->f0000_segment);
1319         samsung->f0000_segment = NULL;
1320     }
1321 
1322     samsung->config = NULL;
1323 }
1324 
1325 static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
1326                       unsigned int ifaceP)
1327 {
1328     const struct sabi_config *config = samsung->config;
1329 
1330     printk(KERN_DEBUG "This computer supports SABI==%x\n",
1331            loca + 0xf0000 - 6);
1332 
1333     printk(KERN_DEBUG "SABI header:\n");
1334     printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
1335            readw(samsung->sabi + config->header_offsets.port));
1336     printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
1337            readb(samsung->sabi + config->header_offsets.iface_func));
1338     printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
1339            readb(samsung->sabi + config->header_offsets.en_mem));
1340     printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
1341            readb(samsung->sabi + config->header_offsets.re_mem));
1342     printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
1343            readw(samsung->sabi + config->header_offsets.data_offset));
1344     printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
1345            readw(samsung->sabi + config->header_offsets.data_segment));
1346 
1347     printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
1348 }
1349 
1350 static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
1351 {
1352     int loca = find_signature(samsung->f0000_segment, "SDiaG@");
1353     int i;
1354 
1355     if (loca == 0xffff)
1356         return ;
1357 
1358     /* Example:
1359      * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
1360      *
1361      * Product name: 90X3A
1362      * BIOS Version: 07HL
1363      */
1364     loca += 1;
1365     for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
1366         char temp = readb(samsung->f0000_segment + loca);
1367 
1368         if (isalnum(temp) || temp == '/' || temp == '-')
1369             samsung->sdiag[i++] = temp;
1370         else
1371             break ;
1372     }
1373 
1374     if (debug && samsung->sdiag[0])
1375         pr_info("sdiag: %s", samsung->sdiag);
1376 }
1377 
1378 static int __init samsung_sabi_init(struct samsung_laptop *samsung)
1379 {
1380     const struct sabi_config *config = NULL;
1381     const struct sabi_commands *commands;
1382     unsigned int ifaceP;
1383     int loca = 0xffff;
1384     int ret = 0;
1385     int i;
1386 
1387     samsung->f0000_segment = ioremap(0xf0000, 0xffff);
1388     if (!samsung->f0000_segment) {
1389         if (debug || force)
1390             pr_err("Can't map the segment at 0xf0000\n");
1391         ret = -EINVAL;
1392         goto exit;
1393     }
1394 
1395     samsung_sabi_diag(samsung);
1396 
1397     /* Try to find one of the signatures in memory to find the header */
1398     for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
1399         samsung->config = &sabi_configs[i];
1400         loca = find_signature(samsung->f0000_segment,
1401                       samsung->config->test_string);
1402         if (loca != 0xffff)
1403             break;
1404     }
1405 
1406     if (loca == 0xffff) {
1407         if (debug || force)
1408             pr_err("This computer does not support SABI\n");
1409         ret = -ENODEV;
1410         goto exit;
1411     }
1412 
1413     config = samsung->config;
1414     commands = &config->commands;
1415 
1416     /* point to the SMI port Number */
1417     loca += 1;
1418     samsung->sabi = (samsung->f0000_segment + loca);
1419 
1420     /* Get a pointer to the SABI Interface */
1421     ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
1422     ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
1423 
1424     if (debug)
1425         samsung_sabi_infos(samsung, loca, ifaceP);
1426 
1427     samsung->sabi_iface = ioremap(ifaceP, 16);
1428     if (!samsung->sabi_iface) {
1429         pr_err("Can't remap %x\n", ifaceP);
1430         ret = -EINVAL;
1431         goto exit;
1432     }
1433 
1434     /* Turn on "Linux" mode in the BIOS */
1435     if (commands->set_linux != 0xff) {
1436         int retval = sabi_set_commandb(samsung,
1437                            commands->set_linux, 0x81);
1438         if (retval) {
1439             pr_warn("Linux mode was not set!\n");
1440             ret = -ENODEV;
1441             goto exit;
1442         }
1443     }
1444 
1445     /* Check for stepping quirk */
1446     if (samsung->handle_backlight)
1447         check_for_stepping_quirk(samsung);
1448 
1449     pr_info("detected SABI interface: %s\n",
1450         samsung->config->test_string);
1451 
1452 exit:
1453     if (ret)
1454         samsung_sabi_exit(samsung);
1455 
1456     return ret;
1457 }
1458 
1459 static void samsung_platform_exit(struct samsung_laptop *samsung)
1460 {
1461     if (samsung->platform_device) {
1462         platform_device_unregister(samsung->platform_device);
1463         samsung->platform_device = NULL;
1464     }
1465 }
1466 
1467 static int samsung_pm_notification(struct notifier_block *nb,
1468                    unsigned long val, void *ptr)
1469 {
1470     struct samsung_laptop *samsung;
1471 
1472     samsung = container_of(nb, struct samsung_laptop, pm_nb);
1473     if (val == PM_POST_HIBERNATION &&
1474         samsung->quirks->enable_kbd_backlight)
1475         kbd_backlight_enable(samsung);
1476 
1477     if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
1478         write_lid_handling(samsung, 1);
1479 
1480     return 0;
1481 }
1482 
1483 static int __init samsung_platform_init(struct samsung_laptop *samsung)
1484 {
1485     struct platform_device *pdev;
1486 
1487     pdev = platform_device_register_simple("samsung", -1, NULL, 0);
1488     if (IS_ERR(pdev))
1489         return PTR_ERR(pdev);
1490 
1491     samsung->platform_device = pdev;
1492     platform_set_drvdata(samsung->platform_device, samsung);
1493     return 0;
1494 }
1495 
1496 static struct samsung_quirks *quirks;
1497 
1498 static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1499 {
1500     quirks = d->driver_data;
1501     return 0;
1502 }
1503 
1504 static const struct dmi_system_id samsung_dmi_table[] __initconst = {
1505     {
1506         .matches = {
1507             DMI_MATCH(DMI_SYS_VENDOR,
1508                     "SAMSUNG ELECTRONICS CO., LTD."),
1509             DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
1510         },
1511     },
1512     {
1513         .matches = {
1514             DMI_MATCH(DMI_SYS_VENDOR,
1515                     "SAMSUNG ELECTRONICS CO., LTD."),
1516             DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
1517         },
1518     },
1519     {
1520         .matches = {
1521             DMI_MATCH(DMI_SYS_VENDOR,
1522                     "SAMSUNG ELECTRONICS CO., LTD."),
1523             DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
1524         },
1525     },
1526     {
1527         .matches = {
1528             DMI_MATCH(DMI_SYS_VENDOR,
1529                     "SAMSUNG ELECTRONICS CO., LTD."),
1530             DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
1531         },
1532     },
1533     /* DMI ids for laptops with bad Chassis Type */
1534     {
1535       .ident = "R40/R41",
1536       .matches = {
1537         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1538         DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1539         DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1540         },
1541     },
1542     /* Specific DMI ids for laptop with quirks */
1543     {
1544      .callback = samsung_dmi_matched,
1545      .ident = "N150P",
1546      .matches = {
1547         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1548         DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
1549         DMI_MATCH(DMI_BOARD_NAME, "N150P"),
1550         },
1551      .driver_data = &samsung_use_native_backlight,
1552     },
1553     {
1554      .callback = samsung_dmi_matched,
1555      .ident = "N145P/N250P/N260P",
1556      .matches = {
1557         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1558         DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1559         DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1560         },
1561      .driver_data = &samsung_use_native_backlight,
1562     },
1563     {
1564      .callback = samsung_dmi_matched,
1565      .ident = "N150/N210/N220",
1566      .matches = {
1567         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1568         DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1569         DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1570         },
1571      .driver_data = &samsung_broken_acpi_video,
1572     },
1573     {
1574      .callback = samsung_dmi_matched,
1575      .ident = "NF110/NF210/NF310",
1576      .matches = {
1577         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1578         DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1579         DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1580         },
1581      .driver_data = &samsung_broken_acpi_video,
1582     },
1583     {
1584      .callback = samsung_dmi_matched,
1585      .ident = "X360",
1586      .matches = {
1587         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1588         DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1589         DMI_MATCH(DMI_BOARD_NAME, "X360"),
1590         },
1591      .driver_data = &samsung_broken_acpi_video,
1592     },
1593     {
1594      .callback = samsung_dmi_matched,
1595      .ident = "N250P",
1596      .matches = {
1597         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1598         DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
1599         DMI_MATCH(DMI_BOARD_NAME, "N250P"),
1600         },
1601      .driver_data = &samsung_use_native_backlight,
1602     },
1603     {
1604      .callback = samsung_dmi_matched,
1605      .ident = "NC210",
1606      .matches = {
1607         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1608         DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1609         DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1610         },
1611      .driver_data = &samsung_broken_acpi_video,
1612     },
1613     {
1614      .callback = samsung_dmi_matched,
1615      .ident = "730U3E/740U3E",
1616      .matches = {
1617         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1618         DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
1619         },
1620      .driver_data = &samsung_np740u3e,
1621     },
1622     {
1623      .callback = samsung_dmi_matched,
1624      .ident = "300V3Z/300V4Z/300V5Z",
1625      .matches = {
1626         DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1627         DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
1628         },
1629      .driver_data = &samsung_lid_handling,
1630     },
1631     { },
1632 };
1633 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1634 
1635 static struct platform_device *samsung_platform_device;
1636 
1637 static int __init samsung_init(void)
1638 {
1639     struct samsung_laptop *samsung;
1640     int ret;
1641 
1642     if (efi_enabled(EFI_BOOT))
1643         return -ENODEV;
1644 
1645     quirks = &samsung_unknown;
1646     if (!force && !dmi_check_system(samsung_dmi_table))
1647         return -ENODEV;
1648 
1649     samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1650     if (!samsung)
1651         return -ENOMEM;
1652 
1653     mutex_init(&samsung->sabi_mutex);
1654     samsung->handle_backlight = true;
1655     samsung->quirks = quirks;
1656 
1657 #ifdef CONFIG_ACPI
1658     if (samsung->quirks->broken_acpi_video)
1659         acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
1660     if (samsung->quirks->use_native_backlight)
1661         acpi_video_set_dmi_backlight_type(acpi_backlight_native);
1662 
1663     if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
1664         samsung->handle_backlight = false;
1665 #endif
1666 
1667     ret = samsung_platform_init(samsung);
1668     if (ret)
1669         goto error_platform;
1670 
1671     ret = samsung_sabi_init(samsung);
1672     if (ret)
1673         goto error_sabi;
1674 
1675     ret = samsung_sysfs_init(samsung);
1676     if (ret)
1677         goto error_sysfs;
1678 
1679     ret = samsung_backlight_init(samsung);
1680     if (ret)
1681         goto error_backlight;
1682 
1683     ret = samsung_rfkill_init(samsung);
1684     if (ret)
1685         goto error_rfkill;
1686 
1687     ret = samsung_leds_init(samsung);
1688     if (ret)
1689         goto error_leds;
1690 
1691     ret = samsung_lid_handling_init(samsung);
1692     if (ret)
1693         goto error_lid_handling;
1694 
1695     samsung_debugfs_init(samsung);
1696 
1697     samsung->pm_nb.notifier_call = samsung_pm_notification;
1698     register_pm_notifier(&samsung->pm_nb);
1699 
1700     samsung_platform_device = samsung->platform_device;
1701     return ret;
1702 
1703 error_lid_handling:
1704     samsung_leds_exit(samsung);
1705 error_leds:
1706     samsung_rfkill_exit(samsung);
1707 error_rfkill:
1708     samsung_backlight_exit(samsung);
1709 error_backlight:
1710     samsung_sysfs_exit(samsung);
1711 error_sysfs:
1712     samsung_sabi_exit(samsung);
1713 error_sabi:
1714     samsung_platform_exit(samsung);
1715 error_platform:
1716     kfree(samsung);
1717     return ret;
1718 }
1719 
1720 static void __exit samsung_exit(void)
1721 {
1722     struct samsung_laptop *samsung;
1723 
1724     samsung = platform_get_drvdata(samsung_platform_device);
1725     unregister_pm_notifier(&samsung->pm_nb);
1726 
1727     samsung_debugfs_exit(samsung);
1728     samsung_lid_handling_exit(samsung);
1729     samsung_leds_exit(samsung);
1730     samsung_rfkill_exit(samsung);
1731     samsung_backlight_exit(samsung);
1732     samsung_sysfs_exit(samsung);
1733     samsung_sabi_exit(samsung);
1734     samsung_platform_exit(samsung);
1735 
1736     kfree(samsung);
1737     samsung_platform_device = NULL;
1738 }
1739 
1740 module_init(samsung_init);
1741 module_exit(samsung_exit);
1742 
1743 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1744 MODULE_DESCRIPTION("Samsung Backlight driver");
1745 MODULE_LICENSE("GPL");