1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
22d70b73aSGreg Kroah-Hartman /*
32d70b73aSGreg Kroah-Hartman * Samsung Laptop driver
42d70b73aSGreg Kroah-Hartman *
52d70b73aSGreg Kroah-Hartman * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
62d70b73aSGreg Kroah-Hartman * Copyright (C) 2009,2011 Novell Inc.
72d70b73aSGreg Kroah-Hartman */
82d70b73aSGreg Kroah-Hartman #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
92d70b73aSGreg Kroah-Hartman
102d70b73aSGreg Kroah-Hartman #include <linux/kernel.h>
112d70b73aSGreg Kroah-Hartman #include <linux/init.h>
122d70b73aSGreg Kroah-Hartman #include <linux/module.h>
132d70b73aSGreg Kroah-Hartman #include <linux/delay.h>
142d70b73aSGreg Kroah-Hartman #include <linux/pci.h>
152d70b73aSGreg Kroah-Hartman #include <linux/backlight.h>
16f674ebf1SCorentin Chary #include <linux/leds.h>
172d70b73aSGreg Kroah-Hartman #include <linux/fb.h>
182d70b73aSGreg Kroah-Hartman #include <linux/dmi.h>
192d70b73aSGreg Kroah-Hartman #include <linux/platform_device.h>
202d70b73aSGreg Kroah-Hartman #include <linux/rfkill.h>
21f34cd9caSCorentin Chary #include <linux/acpi.h>
225b80fc40SCorentin Chary #include <linux/seq_file.h>
235b80fc40SCorentin Chary #include <linux/debugfs.h>
2482c333aaSDavid Rientjes #include <linux/ctype.h>
25e0094244SMatt Fleming #include <linux/efi.h>
260ca849eaSScott Thrasher #include <linux/suspend.h>
27a979e2e2SCorentin Chary #include <acpi/video.h>
282d70b73aSGreg Kroah-Hartman
292d70b73aSGreg Kroah-Hartman /*
302d70b73aSGreg Kroah-Hartman * This driver is needed because a number of Samsung laptops do not hook
312d70b73aSGreg Kroah-Hartman * their control settings through ACPI. So we have to poke around in the
322d70b73aSGreg Kroah-Hartman * BIOS to do things like brightness values, and "special" key controls.
332d70b73aSGreg Kroah-Hartman */
342d70b73aSGreg Kroah-Hartman
352d70b73aSGreg Kroah-Hartman /*
362d70b73aSGreg Kroah-Hartman * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
372d70b73aSGreg Kroah-Hartman * be reserved by the BIOS (which really doesn't make much sense), we tell
382d70b73aSGreg Kroah-Hartman * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
392d70b73aSGreg Kroah-Hartman */
402d70b73aSGreg Kroah-Hartman #define MAX_BRIGHT 0x07
412d70b73aSGreg Kroah-Hartman
422d70b73aSGreg Kroah-Hartman
432d70b73aSGreg Kroah-Hartman #define SABI_IFACE_MAIN 0x00
442d70b73aSGreg Kroah-Hartman #define SABI_IFACE_SUB 0x02
452d70b73aSGreg Kroah-Hartman #define SABI_IFACE_COMPLETE 0x04
462d70b73aSGreg Kroah-Hartman #define SABI_IFACE_DATA 0x05
472d70b73aSGreg Kroah-Hartman
4884d482f2SCorentin Chary #define WL_STATUS_WLAN 0x0
4984d482f2SCorentin Chary #define WL_STATUS_BT 0x2
5084d482f2SCorentin Chary
517e960711SCorentin Chary /* Structure get/set data using sabi */
527e960711SCorentin Chary struct sabi_data {
537e960711SCorentin Chary union {
547e960711SCorentin Chary struct {
557e960711SCorentin Chary u32 d0;
567e960711SCorentin Chary u32 d1;
577e960711SCorentin Chary u16 d2;
587e960711SCorentin Chary u8 d3;
597e960711SCorentin Chary };
607e960711SCorentin Chary u8 data[11];
617e960711SCorentin Chary };
622d70b73aSGreg Kroah-Hartman };
632d70b73aSGreg Kroah-Hartman
642d70b73aSGreg Kroah-Hartman struct sabi_header_offsets {
652d70b73aSGreg Kroah-Hartman u8 port;
662d70b73aSGreg Kroah-Hartman u8 re_mem;
672d70b73aSGreg Kroah-Hartman u8 iface_func;
682d70b73aSGreg Kroah-Hartman u8 en_mem;
692d70b73aSGreg Kroah-Hartman u8 data_offset;
702d70b73aSGreg Kroah-Hartman u8 data_segment;
712d70b73aSGreg Kroah-Hartman };
722d70b73aSGreg Kroah-Hartman
732d70b73aSGreg Kroah-Hartman struct sabi_commands {
742d70b73aSGreg Kroah-Hartman /*
752d70b73aSGreg Kroah-Hartman * Brightness is 0 - 8, as described above.
762d70b73aSGreg Kroah-Hartman * Value 0 is for the BIOS to use
772d70b73aSGreg Kroah-Hartman */
787e960711SCorentin Chary u16 get_brightness;
797e960711SCorentin Chary u16 set_brightness;
802d70b73aSGreg Kroah-Hartman
812d70b73aSGreg Kroah-Hartman /*
822d70b73aSGreg Kroah-Hartman * first byte:
832d70b73aSGreg Kroah-Hartman * 0x00 - wireless is off
842d70b73aSGreg Kroah-Hartman * 0x01 - wireless is on
852d70b73aSGreg Kroah-Hartman * second byte:
862d70b73aSGreg Kroah-Hartman * 0x02 - 3G is off
872d70b73aSGreg Kroah-Hartman * 0x03 - 3G is on
882d70b73aSGreg Kroah-Hartman * TODO, verify 3G is correct, that doesn't seem right...
892d70b73aSGreg Kroah-Hartman */
907e960711SCorentin Chary u16 get_wireless_button;
917e960711SCorentin Chary u16 set_wireless_button;
922d70b73aSGreg Kroah-Hartman
932d70b73aSGreg Kroah-Hartman /* 0 is off, 1 is on */
947e960711SCorentin Chary u16 get_backlight;
957e960711SCorentin Chary u16 set_backlight;
962d70b73aSGreg Kroah-Hartman
972d70b73aSGreg Kroah-Hartman /*
982d70b73aSGreg Kroah-Hartman * 0x80 or 0x00 - no action
992d70b73aSGreg Kroah-Hartman * 0x81 - recovery key pressed
1002d70b73aSGreg Kroah-Hartman */
1017e960711SCorentin Chary u16 get_recovery_mode;
1027e960711SCorentin Chary u16 set_recovery_mode;
1032d70b73aSGreg Kroah-Hartman
1042d70b73aSGreg Kroah-Hartman /*
1052d70b73aSGreg Kroah-Hartman * on seclinux: 0 is low, 1 is high,
1062d70b73aSGreg Kroah-Hartman * on swsmi: 0 is normal, 1 is silent, 2 is turbo
1072d70b73aSGreg Kroah-Hartman */
1087e960711SCorentin Chary u16 get_performance_level;
1097e960711SCorentin Chary u16 set_performance_level;
1102d70b73aSGreg Kroah-Hartman
111cb5b5c91SCorentin Chary /* 0x80 is off, 0x81 is on */
112cb5b5c91SCorentin Chary u16 get_battery_life_extender;
113cb5b5c91SCorentin Chary u16 set_battery_life_extender;
114cb5b5c91SCorentin Chary
1153a75d378SCorentin Chary /* 0x80 is off, 0x81 is on */
1163a75d378SCorentin Chary u16 get_usb_charge;
1173a75d378SCorentin Chary u16 set_usb_charge;
1183a75d378SCorentin Chary
11984d482f2SCorentin Chary /* the first byte is for bluetooth and the third one is for wlan */
12084d482f2SCorentin Chary u16 get_wireless_status;
12184d482f2SCorentin Chary u16 set_wireless_status;
12284d482f2SCorentin Chary
123b0dcaf4fSJulijonas Kikutis /* 0x80 is off, 0x81 is on */
124b0dcaf4fSJulijonas Kikutis u16 get_lid_handling;
125b0dcaf4fSJulijonas Kikutis u16 set_lid_handling;
126b0dcaf4fSJulijonas Kikutis
127f674ebf1SCorentin Chary /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
128f674ebf1SCorentin Chary u16 kbd_backlight;
129f674ebf1SCorentin Chary
1302d70b73aSGreg Kroah-Hartman /*
1312d70b73aSGreg Kroah-Hartman * Tell the BIOS that Linux is running on this machine.
1322d70b73aSGreg Kroah-Hartman * 81 is on, 80 is off
1332d70b73aSGreg Kroah-Hartman */
1347e960711SCorentin Chary u16 set_linux;
1352d70b73aSGreg Kroah-Hartman };
1362d70b73aSGreg Kroah-Hartman
1372d70b73aSGreg Kroah-Hartman struct sabi_performance_level {
1382d70b73aSGreg Kroah-Hartman const char *name;
1397e960711SCorentin Chary u16 value;
1402d70b73aSGreg Kroah-Hartman };
1412d70b73aSGreg Kroah-Hartman
1422d70b73aSGreg Kroah-Hartman struct sabi_config {
14384d482f2SCorentin Chary int sabi_version;
1442d70b73aSGreg Kroah-Hartman const char *test_string;
1452d70b73aSGreg Kroah-Hartman u16 main_function;
1462d70b73aSGreg Kroah-Hartman const struct sabi_header_offsets header_offsets;
1472d70b73aSGreg Kroah-Hartman const struct sabi_commands commands;
1482d70b73aSGreg Kroah-Hartman const struct sabi_performance_level performance_levels[4];
1492d70b73aSGreg Kroah-Hartman u8 min_brightness;
1502d70b73aSGreg Kroah-Hartman u8 max_brightness;
1512d70b73aSGreg Kroah-Hartman };
1522d70b73aSGreg Kroah-Hartman
1532d70b73aSGreg Kroah-Hartman static const struct sabi_config sabi_configs[] = {
1542d70b73aSGreg Kroah-Hartman {
1553e58e1c4SJason Wang /* I don't know if it is really 2, but it is
15684d482f2SCorentin Chary * less than 3 anyway */
15784d482f2SCorentin Chary .sabi_version = 2,
15884d482f2SCorentin Chary
1592d70b73aSGreg Kroah-Hartman .test_string = "SECLINUX",
1602d70b73aSGreg Kroah-Hartman
1612d70b73aSGreg Kroah-Hartman .main_function = 0x4c49,
1622d70b73aSGreg Kroah-Hartman
1632d70b73aSGreg Kroah-Hartman .header_offsets = {
1642d70b73aSGreg Kroah-Hartman .port = 0x00,
1652d70b73aSGreg Kroah-Hartman .re_mem = 0x02,
1662d70b73aSGreg Kroah-Hartman .iface_func = 0x03,
1672d70b73aSGreg Kroah-Hartman .en_mem = 0x04,
1682d70b73aSGreg Kroah-Hartman .data_offset = 0x05,
1692d70b73aSGreg Kroah-Hartman .data_segment = 0x07,
1702d70b73aSGreg Kroah-Hartman },
1712d70b73aSGreg Kroah-Hartman
1722d70b73aSGreg Kroah-Hartman .commands = {
1732d70b73aSGreg Kroah-Hartman .get_brightness = 0x00,
1742d70b73aSGreg Kroah-Hartman .set_brightness = 0x01,
1752d70b73aSGreg Kroah-Hartman
1762d70b73aSGreg Kroah-Hartman .get_wireless_button = 0x02,
1772d70b73aSGreg Kroah-Hartman .set_wireless_button = 0x03,
1782d70b73aSGreg Kroah-Hartman
1792d70b73aSGreg Kroah-Hartman .get_backlight = 0x04,
1802d70b73aSGreg Kroah-Hartman .set_backlight = 0x05,
1812d70b73aSGreg Kroah-Hartman
1822d70b73aSGreg Kroah-Hartman .get_recovery_mode = 0x06,
1832d70b73aSGreg Kroah-Hartman .set_recovery_mode = 0x07,
1842d70b73aSGreg Kroah-Hartman
1852d70b73aSGreg Kroah-Hartman .get_performance_level = 0x08,
1862d70b73aSGreg Kroah-Hartman .set_performance_level = 0x09,
1872d70b73aSGreg Kroah-Hartman
188cb5b5c91SCorentin Chary .get_battery_life_extender = 0xFFFF,
189cb5b5c91SCorentin Chary .set_battery_life_extender = 0xFFFF,
190cb5b5c91SCorentin Chary
1913a75d378SCorentin Chary .get_usb_charge = 0xFFFF,
1923a75d378SCorentin Chary .set_usb_charge = 0xFFFF,
1933a75d378SCorentin Chary
19484d482f2SCorentin Chary .get_wireless_status = 0xFFFF,
19584d482f2SCorentin Chary .set_wireless_status = 0xFFFF,
19684d482f2SCorentin Chary
197b0dcaf4fSJulijonas Kikutis .get_lid_handling = 0xFFFF,
198b0dcaf4fSJulijonas Kikutis .set_lid_handling = 0xFFFF,
199b0dcaf4fSJulijonas Kikutis
200f674ebf1SCorentin Chary .kbd_backlight = 0xFFFF,
201f674ebf1SCorentin Chary
2022d70b73aSGreg Kroah-Hartman .set_linux = 0x0a,
2032d70b73aSGreg Kroah-Hartman },
2042d70b73aSGreg Kroah-Hartman
2052d70b73aSGreg Kroah-Hartman .performance_levels = {
2062d70b73aSGreg Kroah-Hartman {
2072d70b73aSGreg Kroah-Hartman .name = "silent",
2082d70b73aSGreg Kroah-Hartman .value = 0,
2092d70b73aSGreg Kroah-Hartman },
2102d70b73aSGreg Kroah-Hartman {
2112d70b73aSGreg Kroah-Hartman .name = "normal",
2122d70b73aSGreg Kroah-Hartman .value = 1,
2132d70b73aSGreg Kroah-Hartman },
2142d70b73aSGreg Kroah-Hartman { },
2152d70b73aSGreg Kroah-Hartman },
2162d70b73aSGreg Kroah-Hartman .min_brightness = 1,
2172d70b73aSGreg Kroah-Hartman .max_brightness = 8,
2182d70b73aSGreg Kroah-Hartman },
2192d70b73aSGreg Kroah-Hartman {
22084d482f2SCorentin Chary .sabi_version = 3,
22184d482f2SCorentin Chary
2222d70b73aSGreg Kroah-Hartman .test_string = "SwSmi@",
2232d70b73aSGreg Kroah-Hartman
2242d70b73aSGreg Kroah-Hartman .main_function = 0x5843,
2252d70b73aSGreg Kroah-Hartman
2262d70b73aSGreg Kroah-Hartman .header_offsets = {
2272d70b73aSGreg Kroah-Hartman .port = 0x00,
2282d70b73aSGreg Kroah-Hartman .re_mem = 0x04,
2292d70b73aSGreg Kroah-Hartman .iface_func = 0x02,
2302d70b73aSGreg Kroah-Hartman .en_mem = 0x03,
2312d70b73aSGreg Kroah-Hartman .data_offset = 0x05,
2322d70b73aSGreg Kroah-Hartman .data_segment = 0x07,
2332d70b73aSGreg Kroah-Hartman },
2342d70b73aSGreg Kroah-Hartman
2352d70b73aSGreg Kroah-Hartman .commands = {
2362d70b73aSGreg Kroah-Hartman .get_brightness = 0x10,
2372d70b73aSGreg Kroah-Hartman .set_brightness = 0x11,
2382d70b73aSGreg Kroah-Hartman
2392d70b73aSGreg Kroah-Hartman .get_wireless_button = 0x12,
2402d70b73aSGreg Kroah-Hartman .set_wireless_button = 0x13,
2412d70b73aSGreg Kroah-Hartman
2422d70b73aSGreg Kroah-Hartman .get_backlight = 0x2d,
2432d70b73aSGreg Kroah-Hartman .set_backlight = 0x2e,
2442d70b73aSGreg Kroah-Hartman
2452d70b73aSGreg Kroah-Hartman .get_recovery_mode = 0xff,
2462d70b73aSGreg Kroah-Hartman .set_recovery_mode = 0xff,
2472d70b73aSGreg Kroah-Hartman
2482d70b73aSGreg Kroah-Hartman .get_performance_level = 0x31,
2492d70b73aSGreg Kroah-Hartman .set_performance_level = 0x32,
2502d70b73aSGreg Kroah-Hartman
251cb5b5c91SCorentin Chary .get_battery_life_extender = 0x65,
252cb5b5c91SCorentin Chary .set_battery_life_extender = 0x66,
253cb5b5c91SCorentin Chary
2543a75d378SCorentin Chary .get_usb_charge = 0x67,
2553a75d378SCorentin Chary .set_usb_charge = 0x68,
2563a75d378SCorentin Chary
25784d482f2SCorentin Chary .get_wireless_status = 0x69,
25884d482f2SCorentin Chary .set_wireless_status = 0x6a,
25984d482f2SCorentin Chary
260b0dcaf4fSJulijonas Kikutis .get_lid_handling = 0x6d,
261b0dcaf4fSJulijonas Kikutis .set_lid_handling = 0x6e,
262b0dcaf4fSJulijonas Kikutis
263f674ebf1SCorentin Chary .kbd_backlight = 0x78,
264f674ebf1SCorentin Chary
2652d70b73aSGreg Kroah-Hartman .set_linux = 0xff,
2662d70b73aSGreg Kroah-Hartman },
2672d70b73aSGreg Kroah-Hartman
2682d70b73aSGreg Kroah-Hartman .performance_levels = {
2692d70b73aSGreg Kroah-Hartman {
2702d70b73aSGreg Kroah-Hartman .name = "normal",
2712d70b73aSGreg Kroah-Hartman .value = 0,
2722d70b73aSGreg Kroah-Hartman },
2732d70b73aSGreg Kroah-Hartman {
2742d70b73aSGreg Kroah-Hartman .name = "silent",
2752d70b73aSGreg Kroah-Hartman .value = 1,
2762d70b73aSGreg Kroah-Hartman },
2772d70b73aSGreg Kroah-Hartman {
2782d70b73aSGreg Kroah-Hartman .name = "overclock",
2792d70b73aSGreg Kroah-Hartman .value = 2,
2802d70b73aSGreg Kroah-Hartman },
2812d70b73aSGreg Kroah-Hartman { },
2822d70b73aSGreg Kroah-Hartman },
2832d70b73aSGreg Kroah-Hartman .min_brightness = 0,
2842d70b73aSGreg Kroah-Hartman .max_brightness = 8,
2852d70b73aSGreg Kroah-Hartman },
2862d70b73aSGreg Kroah-Hartman { },
2872d70b73aSGreg Kroah-Hartman };
2882d70b73aSGreg Kroah-Hartman
2895b80fc40SCorentin Chary /*
2905b80fc40SCorentin Chary * samsung-laptop/ - debugfs root directory
2915b80fc40SCorentin Chary * f0000_segment - dump f0000 segment
2925b80fc40SCorentin Chary * command - current command
2935b80fc40SCorentin Chary * data - current data
2945b80fc40SCorentin Chary * d0, d1, d2, d3 - data fields
2955b80fc40SCorentin Chary * call - call SABI using command and data
2965b80fc40SCorentin Chary *
2975b80fc40SCorentin Chary * This allow to call arbitrary sabi commands wihout
2985b80fc40SCorentin Chary * modifying the driver at all.
2995b80fc40SCorentin Chary * For example, setting the keyboard backlight brightness to 5
3005b80fc40SCorentin Chary *
3015b80fc40SCorentin Chary * echo 0x78 > command
3025b80fc40SCorentin Chary * echo 0x0582 > d0
3035b80fc40SCorentin Chary * echo 0 > d1
3045b80fc40SCorentin Chary * echo 0 > d2
3055b80fc40SCorentin Chary * echo 0 > d3
3065b80fc40SCorentin Chary * cat call
3075b80fc40SCorentin Chary */
3085b80fc40SCorentin Chary
3095b80fc40SCorentin Chary struct samsung_laptop_debug {
3105b80fc40SCorentin Chary struct dentry *root;
3115b80fc40SCorentin Chary struct sabi_data data;
3125b80fc40SCorentin Chary u16 command;
3135b80fc40SCorentin Chary
3145b80fc40SCorentin Chary struct debugfs_blob_wrapper f0000_wrapper;
3155b80fc40SCorentin Chary struct debugfs_blob_wrapper data_wrapper;
3166f6ae06eSCorentin Chary struct debugfs_blob_wrapper sdiag_wrapper;
3175b80fc40SCorentin Chary };
3185b80fc40SCorentin Chary
31984d482f2SCorentin Chary struct samsung_laptop;
32084d482f2SCorentin Chary
32184d482f2SCorentin Chary struct samsung_rfkill {
32284d482f2SCorentin Chary struct samsung_laptop *samsung;
32384d482f2SCorentin Chary struct rfkill *rfkill;
32484d482f2SCorentin Chary enum rfkill_type type;
32584d482f2SCorentin Chary };
32684d482f2SCorentin Chary
327a6df4894SCorentin Chary struct samsung_laptop {
328a6df4894SCorentin Chary const struct sabi_config *config;
3292d70b73aSGreg Kroah-Hartman
330a6df4894SCorentin Chary void __iomem *sabi;
331a6df4894SCorentin Chary void __iomem *sabi_iface;
332a6df4894SCorentin Chary void __iomem *f0000_segment;
333a6df4894SCorentin Chary
334a6df4894SCorentin Chary struct mutex sabi_mutex;
335a6df4894SCorentin Chary
3365dea7a20SCorentin Chary struct platform_device *platform_device;
337a6df4894SCorentin Chary struct backlight_device *backlight_device;
33884d482f2SCorentin Chary
33984d482f2SCorentin Chary struct samsung_rfkill wlan;
34084d482f2SCorentin Chary struct samsung_rfkill bluetooth;
341a6df4894SCorentin Chary
342f674ebf1SCorentin Chary struct led_classdev kbd_led;
343f674ebf1SCorentin Chary int kbd_led_wk;
344f674ebf1SCorentin Chary struct workqueue_struct *led_workqueue;
345f674ebf1SCorentin Chary struct work_struct kbd_led_work;
346f674ebf1SCorentin Chary
3475b80fc40SCorentin Chary struct samsung_laptop_debug debug;
348a979e2e2SCorentin Chary struct samsung_quirks *quirks;
3495b80fc40SCorentin Chary
3500ca849eaSScott Thrasher struct notifier_block pm_nb;
3510ca849eaSScott Thrasher
352f34cd9caSCorentin Chary bool handle_backlight;
353a6df4894SCorentin Chary bool has_stepping_quirk;
3546f6ae06eSCorentin Chary
3556f6ae06eSCorentin Chary char sdiag[64];
356a6df4894SCorentin Chary };
357a6df4894SCorentin Chary
358a979e2e2SCorentin Chary struct samsung_quirks {
3590ca849eaSScott Thrasher bool four_kbd_backlight_levels;
3600ca849eaSScott Thrasher bool enable_kbd_backlight;
361b0dcaf4fSJulijonas Kikutis bool lid_handling;
362a979e2e2SCorentin Chary };
3635dea7a20SCorentin Chary
364a979e2e2SCorentin Chary static struct samsung_quirks samsung_unknown = {};
365a979e2e2SCorentin Chary
3660ca849eaSScott Thrasher static struct samsung_quirks samsung_np740u3e = {
3670ca849eaSScott Thrasher .four_kbd_backlight_levels = true,
3680ca849eaSScott Thrasher .enable_kbd_backlight = true,
3690ca849eaSScott Thrasher };
3700ca849eaSScott Thrasher
371b0dcaf4fSJulijonas Kikutis static struct samsung_quirks samsung_lid_handling = {
372b0dcaf4fSJulijonas Kikutis .lid_handling = true,
373b0dcaf4fSJulijonas Kikutis };
374b0dcaf4fSJulijonas Kikutis
37590ab5ee9SRusty Russell static bool force;
3762d70b73aSGreg Kroah-Hartman module_param(force, bool, 0);
3772d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(force,
3782d70b73aSGreg Kroah-Hartman "Disable the DMI check and forces the driver to be loaded");
3792d70b73aSGreg Kroah-Hartman
38090ab5ee9SRusty Russell static bool debug;
3815b6a9a2fSWolfram Sang module_param(debug, bool, 0644);
3822d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(debug, "Debug enabled or not");
3832d70b73aSGreg Kroah-Hartman
sabi_command(struct samsung_laptop * samsung,u16 command,struct sabi_data * in,struct sabi_data * out)3847e960711SCorentin Chary static int sabi_command(struct samsung_laptop *samsung, u16 command,
3857e960711SCorentin Chary struct sabi_data *in,
3867e960711SCorentin Chary struct sabi_data *out)
3872d70b73aSGreg Kroah-Hartman {
388a6df4894SCorentin Chary const struct sabi_config *config = samsung->config;
3897e960711SCorentin Chary int ret = 0;
390a6df4894SCorentin Chary u16 port = readw(samsung->sabi + config->header_offsets.port);
3912d70b73aSGreg Kroah-Hartman u8 complete, iface_data;
3922d70b73aSGreg Kroah-Hartman
393a6df4894SCorentin Chary mutex_lock(&samsung->sabi_mutex);
3942d70b73aSGreg Kroah-Hartman
3957e960711SCorentin Chary if (debug) {
3967e960711SCorentin Chary if (in)
3972e777187SCorentin Chary pr_info("SABI command:0x%04x "
3982e777187SCorentin Chary "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
3997e960711SCorentin Chary command, in->d0, in->d1, in->d2, in->d3);
4007e960711SCorentin Chary else
4012e777187SCorentin Chary pr_info("SABI command:0x%04x", command);
4027e960711SCorentin Chary }
4037e960711SCorentin Chary
4042d70b73aSGreg Kroah-Hartman /* enable memory to be able to write to it */
405a6df4894SCorentin Chary outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
4062d70b73aSGreg Kroah-Hartman
4072d70b73aSGreg Kroah-Hartman /* write out the command */
408a6df4894SCorentin Chary writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
409a6df4894SCorentin Chary writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
410a6df4894SCorentin Chary writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
4117e960711SCorentin Chary if (in) {
4127e960711SCorentin Chary writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
4137e960711SCorentin Chary writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
4147e960711SCorentin Chary writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
4157e960711SCorentin Chary writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
4167e960711SCorentin Chary }
417a6df4894SCorentin Chary outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
4182d70b73aSGreg Kroah-Hartman
4192d70b73aSGreg Kroah-Hartman /* write protect memory to make it safe */
420a6df4894SCorentin Chary outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
4212d70b73aSGreg Kroah-Hartman
4222d70b73aSGreg Kroah-Hartman /* see if the command actually succeeded */
423a6df4894SCorentin Chary complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
424a6df4894SCorentin Chary iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
4252e777187SCorentin Chary
4262e777187SCorentin Chary /* iface_data = 0xFF happens when a command is not known
4272e777187SCorentin Chary * so we only add a warning in debug mode since we will
4282e777187SCorentin Chary * probably issue some unknown command at startup to find
4292e777187SCorentin Chary * out which features are supported */
4302e777187SCorentin Chary if (complete != 0xaa || (iface_data == 0xff && debug))
4317e960711SCorentin Chary pr_warn("SABI command 0x%04x failed with"
4327e960711SCorentin Chary " completion flag 0x%02x and interface data 0x%02x",
4332d70b73aSGreg Kroah-Hartman command, complete, iface_data);
4342e777187SCorentin Chary
4352e777187SCorentin Chary if (complete != 0xaa || iface_data == 0xff) {
4367e960711SCorentin Chary ret = -EINVAL;
4372d70b73aSGreg Kroah-Hartman goto exit;
4382d70b73aSGreg Kroah-Hartman }
4397e960711SCorentin Chary
4407e960711SCorentin Chary if (out) {
4417e960711SCorentin Chary out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
4427e960711SCorentin Chary out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
4437e960711SCorentin Chary out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
4447e960711SCorentin Chary out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
4457e960711SCorentin Chary }
4467e960711SCorentin Chary
4477e960711SCorentin Chary if (debug && out) {
4482e777187SCorentin Chary pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
4497e960711SCorentin Chary out->d0, out->d1, out->d2, out->d3);
4507e960711SCorentin Chary }
4512d70b73aSGreg Kroah-Hartman
4522d70b73aSGreg Kroah-Hartman exit:
453a6df4894SCorentin Chary mutex_unlock(&samsung->sabi_mutex);
4547e960711SCorentin Chary return ret;
4552d70b73aSGreg Kroah-Hartman }
4562d70b73aSGreg Kroah-Hartman
4577e960711SCorentin Chary /* simple wrappers usable with most commands */
sabi_set_commandb(struct samsung_laptop * samsung,u16 command,u8 data)4587e960711SCorentin Chary static int sabi_set_commandb(struct samsung_laptop *samsung,
4597e960711SCorentin Chary u16 command, u8 data)
4602d70b73aSGreg Kroah-Hartman {
46185229440SDavid Rientjes struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
4622d70b73aSGreg Kroah-Hartman
4637e960711SCorentin Chary in.data[0] = data;
4647e960711SCorentin Chary return sabi_command(samsung, command, &in, NULL);
4652d70b73aSGreg Kroah-Hartman }
4662d70b73aSGreg Kroah-Hartman
read_brightness(struct samsung_laptop * samsung)4675dea7a20SCorentin Chary static int read_brightness(struct samsung_laptop *samsung)
4682d70b73aSGreg Kroah-Hartman {
469a6df4894SCorentin Chary const struct sabi_config *config = samsung->config;
470a6df4894SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
4717e960711SCorentin Chary struct sabi_data sretval;
4722d70b73aSGreg Kroah-Hartman int user_brightness = 0;
4732d70b73aSGreg Kroah-Hartman int retval;
4742d70b73aSGreg Kroah-Hartman
4757e960711SCorentin Chary retval = sabi_command(samsung, commands->get_brightness,
4767e960711SCorentin Chary NULL, &sretval);
4777e960711SCorentin Chary if (retval)
4787e960711SCorentin Chary return retval;
4797e960711SCorentin Chary
4807e960711SCorentin Chary user_brightness = sretval.data[0];
481a6df4894SCorentin Chary if (user_brightness > config->min_brightness)
482a6df4894SCorentin Chary user_brightness -= config->min_brightness;
483bee460beSJason Stubbs else
484bee460beSJason Stubbs user_brightness = 0;
4857e960711SCorentin Chary
4862d70b73aSGreg Kroah-Hartman return user_brightness;
4872d70b73aSGreg Kroah-Hartman }
4882d70b73aSGreg Kroah-Hartman
set_brightness(struct samsung_laptop * samsung,u8 user_brightness)4895dea7a20SCorentin Chary static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
4902d70b73aSGreg Kroah-Hartman {
491a6df4894SCorentin Chary const struct sabi_config *config = samsung->config;
492a6df4894SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
493a6df4894SCorentin Chary u8 user_level = user_brightness + config->min_brightness;
4942d70b73aSGreg Kroah-Hartman
495a6df4894SCorentin Chary if (samsung->has_stepping_quirk && user_level != 0) {
496ac080523SJason Stubbs /*
497ac080523SJason Stubbs * short circuit if the specified level is what's already set
498ac080523SJason Stubbs * to prevent the screen from flickering needlessly
499ac080523SJason Stubbs */
5005dea7a20SCorentin Chary if (user_brightness == read_brightness(samsung))
501ac080523SJason Stubbs return;
502ac080523SJason Stubbs
5037e960711SCorentin Chary sabi_set_commandb(samsung, commands->set_brightness, 0);
504ac080523SJason Stubbs }
505ac080523SJason Stubbs
5067e960711SCorentin Chary sabi_set_commandb(samsung, commands->set_brightness, user_level);
5072d70b73aSGreg Kroah-Hartman }
5082d70b73aSGreg Kroah-Hartman
get_brightness(struct backlight_device * bd)5092d70b73aSGreg Kroah-Hartman static int get_brightness(struct backlight_device *bd)
5102d70b73aSGreg Kroah-Hartman {
5115dea7a20SCorentin Chary struct samsung_laptop *samsung = bl_get_data(bd);
5125dea7a20SCorentin Chary
5135dea7a20SCorentin Chary return read_brightness(samsung);
5142d70b73aSGreg Kroah-Hartman }
5152d70b73aSGreg Kroah-Hartman
check_for_stepping_quirk(struct samsung_laptop * samsung)5165dea7a20SCorentin Chary static void check_for_stepping_quirk(struct samsung_laptop *samsung)
517ac080523SJason Stubbs {
5185dea7a20SCorentin Chary int initial_level;
5195dea7a20SCorentin Chary int check_level;
5205dea7a20SCorentin Chary int orig_level = read_brightness(samsung);
521ac080523SJason Stubbs
522ac080523SJason Stubbs /*
523ac080523SJason Stubbs * Some laptops exhibit the strange behaviour of stepping toward
524ac080523SJason Stubbs * (rather than setting) the brightness except when changing to/from
525ac080523SJason Stubbs * brightness level 0. This behaviour is checked for here and worked
526ac080523SJason Stubbs * around in set_brightness.
527ac080523SJason Stubbs */
528ac080523SJason Stubbs
529ba05b237SJohn Serock if (orig_level == 0)
5305dea7a20SCorentin Chary set_brightness(samsung, 1);
531ba05b237SJohn Serock
5325dea7a20SCorentin Chary initial_level = read_brightness(samsung);
533ba05b237SJohn Serock
534ac080523SJason Stubbs if (initial_level <= 2)
535ac080523SJason Stubbs check_level = initial_level + 2;
536ac080523SJason Stubbs else
537ac080523SJason Stubbs check_level = initial_level - 2;
538ac080523SJason Stubbs
539a6df4894SCorentin Chary samsung->has_stepping_quirk = false;
5405dea7a20SCorentin Chary set_brightness(samsung, check_level);
541ac080523SJason Stubbs
5425dea7a20SCorentin Chary if (read_brightness(samsung) != check_level) {
543a6df4894SCorentin Chary samsung->has_stepping_quirk = true;
544ac080523SJason Stubbs pr_info("enabled workaround for brightness stepping quirk\n");
545ac080523SJason Stubbs }
546ac080523SJason Stubbs
5475dea7a20SCorentin Chary set_brightness(samsung, orig_level);
548ac080523SJason Stubbs }
549ac080523SJason Stubbs
update_status(struct backlight_device * bd)5502d70b73aSGreg Kroah-Hartman static int update_status(struct backlight_device *bd)
5512d70b73aSGreg Kroah-Hartman {
5525dea7a20SCorentin Chary struct samsung_laptop *samsung = bl_get_data(bd);
553a6df4894SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
554a6df4894SCorentin Chary
5555dea7a20SCorentin Chary set_brightness(samsung, bd->props.brightness);
5562d70b73aSGreg Kroah-Hartman
5572d70b73aSGreg Kroah-Hartman if (bd->props.power == FB_BLANK_UNBLANK)
5587e960711SCorentin Chary sabi_set_commandb(samsung, commands->set_backlight, 1);
5592d70b73aSGreg Kroah-Hartman else
5607e960711SCorentin Chary sabi_set_commandb(samsung, commands->set_backlight, 0);
5615dea7a20SCorentin Chary
5622d70b73aSGreg Kroah-Hartman return 0;
5632d70b73aSGreg Kroah-Hartman }
5642d70b73aSGreg Kroah-Hartman
5652d70b73aSGreg Kroah-Hartman static const struct backlight_ops backlight_ops = {
5662d70b73aSGreg Kroah-Hartman .get_brightness = get_brightness,
5672d70b73aSGreg Kroah-Hartman .update_status = update_status,
5682d70b73aSGreg Kroah-Hartman };
5692d70b73aSGreg Kroah-Hartman
seclinux_rfkill_set(void * data,bool blocked)57084d482f2SCorentin Chary static int seclinux_rfkill_set(void *data, bool blocked)
5712d70b73aSGreg Kroah-Hartman {
57220db88e3SCorentin Chary struct samsung_rfkill *srfkill = data;
57320db88e3SCorentin Chary struct samsung_laptop *samsung = srfkill->samsung;
574a6df4894SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
575a6df4894SCorentin Chary
57684d482f2SCorentin Chary return sabi_set_commandb(samsung, commands->set_wireless_button,
57784d482f2SCorentin Chary !blocked);
5782d70b73aSGreg Kroah-Hartman }
5792d70b73aSGreg Kroah-Hartman
5808314a1c8SBhumika Goyal static const struct rfkill_ops seclinux_rfkill_ops = {
58184d482f2SCorentin Chary .set_block = seclinux_rfkill_set,
58284d482f2SCorentin Chary };
58384d482f2SCorentin Chary
swsmi_wireless_status(struct samsung_laptop * samsung,struct sabi_data * data)58484d482f2SCorentin Chary static int swsmi_wireless_status(struct samsung_laptop *samsung,
58584d482f2SCorentin Chary struct sabi_data *data)
58684d482f2SCorentin Chary {
58784d482f2SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
58884d482f2SCorentin Chary
58984d482f2SCorentin Chary return sabi_command(samsung, commands->get_wireless_status,
59084d482f2SCorentin Chary NULL, data);
59184d482f2SCorentin Chary }
59284d482f2SCorentin Chary
swsmi_rfkill_set(void * priv,bool blocked)59384d482f2SCorentin Chary static int swsmi_rfkill_set(void *priv, bool blocked)
59484d482f2SCorentin Chary {
59584d482f2SCorentin Chary struct samsung_rfkill *srfkill = priv;
59684d482f2SCorentin Chary struct samsung_laptop *samsung = srfkill->samsung;
59784d482f2SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
59884d482f2SCorentin Chary struct sabi_data data;
59984d482f2SCorentin Chary int ret, i;
60084d482f2SCorentin Chary
60184d482f2SCorentin Chary ret = swsmi_wireless_status(samsung, &data);
60284d482f2SCorentin Chary if (ret)
60384d482f2SCorentin Chary return ret;
60484d482f2SCorentin Chary
60584d482f2SCorentin Chary /* Don't set the state for non-present devices */
60684d482f2SCorentin Chary for (i = 0; i < 4; i++)
60784d482f2SCorentin Chary if (data.data[i] == 0x02)
60884d482f2SCorentin Chary data.data[1] = 0;
60984d482f2SCorentin Chary
61084d482f2SCorentin Chary if (srfkill->type == RFKILL_TYPE_WLAN)
61184d482f2SCorentin Chary data.data[WL_STATUS_WLAN] = !blocked;
61284d482f2SCorentin Chary else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
61384d482f2SCorentin Chary data.data[WL_STATUS_BT] = !blocked;
61484d482f2SCorentin Chary
61584d482f2SCorentin Chary return sabi_command(samsung, commands->set_wireless_status,
61684d482f2SCorentin Chary &data, &data);
61784d482f2SCorentin Chary }
61884d482f2SCorentin Chary
swsmi_rfkill_query(struct rfkill * rfkill,void * priv)61984d482f2SCorentin Chary static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
62084d482f2SCorentin Chary {
62184d482f2SCorentin Chary struct samsung_rfkill *srfkill = priv;
62284d482f2SCorentin Chary struct samsung_laptop *samsung = srfkill->samsung;
62384d482f2SCorentin Chary struct sabi_data data;
62484d482f2SCorentin Chary int ret;
62584d482f2SCorentin Chary
62684d482f2SCorentin Chary ret = swsmi_wireless_status(samsung, &data);
62784d482f2SCorentin Chary if (ret)
62884d482f2SCorentin Chary return ;
62984d482f2SCorentin Chary
63084d482f2SCorentin Chary if (srfkill->type == RFKILL_TYPE_WLAN)
63184d482f2SCorentin Chary ret = data.data[WL_STATUS_WLAN];
63284d482f2SCorentin Chary else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
63384d482f2SCorentin Chary ret = data.data[WL_STATUS_BT];
63484d482f2SCorentin Chary else
63584d482f2SCorentin Chary return ;
63684d482f2SCorentin Chary
63784d482f2SCorentin Chary rfkill_set_sw_state(rfkill, !ret);
63884d482f2SCorentin Chary }
63984d482f2SCorentin Chary
6408314a1c8SBhumika Goyal static const struct rfkill_ops swsmi_rfkill_ops = {
64184d482f2SCorentin Chary .set_block = swsmi_rfkill_set,
64284d482f2SCorentin Chary .query = swsmi_rfkill_query,
6432d70b73aSGreg Kroah-Hartman };
6442d70b73aSGreg Kroah-Hartman
get_performance_level(struct device * dev,struct device_attribute * attr,char * buf)6452d70b73aSGreg Kroah-Hartman static ssize_t get_performance_level(struct device *dev,
6462d70b73aSGreg Kroah-Hartman struct device_attribute *attr, char *buf)
6472d70b73aSGreg Kroah-Hartman {
6485dea7a20SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
649a6df4894SCorentin Chary const struct sabi_config *config = samsung->config;
6505dea7a20SCorentin Chary const struct sabi_commands *commands = &config->commands;
6517e960711SCorentin Chary struct sabi_data sretval;
6522d70b73aSGreg Kroah-Hartman int retval;
6532d70b73aSGreg Kroah-Hartman int i;
6542d70b73aSGreg Kroah-Hartman
6552d70b73aSGreg Kroah-Hartman /* Read the state */
6567e960711SCorentin Chary retval = sabi_command(samsung, commands->get_performance_level,
6577e960711SCorentin Chary NULL, &sretval);
6582d70b73aSGreg Kroah-Hartman if (retval)
6592d70b73aSGreg Kroah-Hartman return retval;
6602d70b73aSGreg Kroah-Hartman
6612d70b73aSGreg Kroah-Hartman /* The logic is backwards, yeah, lots of fun... */
662a6df4894SCorentin Chary for (i = 0; config->performance_levels[i].name; ++i) {
6637e960711SCorentin Chary if (sretval.data[0] == config->performance_levels[i].value)
664a6df4894SCorentin Chary return sprintf(buf, "%s\n", config->performance_levels[i].name);
6652d70b73aSGreg Kroah-Hartman }
6662d70b73aSGreg Kroah-Hartman return sprintf(buf, "%s\n", "unknown");
6672d70b73aSGreg Kroah-Hartman }
6682d70b73aSGreg Kroah-Hartman
set_performance_level(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)6692d70b73aSGreg Kroah-Hartman static ssize_t set_performance_level(struct device *dev,
6702d70b73aSGreg Kroah-Hartman struct device_attribute *attr, const char *buf,
6712d70b73aSGreg Kroah-Hartman size_t count)
6722d70b73aSGreg Kroah-Hartman {
6735dea7a20SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
674a6df4894SCorentin Chary const struct sabi_config *config = samsung->config;
6755dea7a20SCorentin Chary const struct sabi_commands *commands = &config->commands;
6762d70b73aSGreg Kroah-Hartman int i;
6775dea7a20SCorentin Chary
6785dea7a20SCorentin Chary if (count < 1)
6795dea7a20SCorentin Chary return count;
6805dea7a20SCorentin Chary
681a6df4894SCorentin Chary for (i = 0; config->performance_levels[i].name; ++i) {
6822d70b73aSGreg Kroah-Hartman const struct sabi_performance_level *level =
683a6df4894SCorentin Chary &config->performance_levels[i];
6842d70b73aSGreg Kroah-Hartman if (!strncasecmp(level->name, buf, strlen(level->name))) {
6857e960711SCorentin Chary sabi_set_commandb(samsung,
6865dea7a20SCorentin Chary commands->set_performance_level,
6872d70b73aSGreg Kroah-Hartman level->value);
6882d70b73aSGreg Kroah-Hartman break;
6892d70b73aSGreg Kroah-Hartman }
6902d70b73aSGreg Kroah-Hartman }
6915dea7a20SCorentin Chary
692a6df4894SCorentin Chary if (!config->performance_levels[i].name)
6932d70b73aSGreg Kroah-Hartman return -EINVAL;
6945dea7a20SCorentin Chary
6952d70b73aSGreg Kroah-Hartman return count;
6962d70b73aSGreg Kroah-Hartman }
6975dea7a20SCorentin Chary
6985b6a9a2fSWolfram Sang static DEVICE_ATTR(performance_level, 0644,
6992d70b73aSGreg Kroah-Hartman get_performance_level, set_performance_level);
7002d70b73aSGreg Kroah-Hartman
read_battery_life_extender(struct samsung_laptop * samsung)701cb5b5c91SCorentin Chary static int read_battery_life_extender(struct samsung_laptop *samsung)
702cb5b5c91SCorentin Chary {
703cb5b5c91SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
704cb5b5c91SCorentin Chary struct sabi_data data;
705cb5b5c91SCorentin Chary int retval;
706cb5b5c91SCorentin Chary
707cb5b5c91SCorentin Chary if (commands->get_battery_life_extender == 0xFFFF)
708cb5b5c91SCorentin Chary return -ENODEV;
709cb5b5c91SCorentin Chary
710cb5b5c91SCorentin Chary memset(&data, 0, sizeof(data));
711cb5b5c91SCorentin Chary data.data[0] = 0x80;
712cb5b5c91SCorentin Chary retval = sabi_command(samsung, commands->get_battery_life_extender,
713cb5b5c91SCorentin Chary &data, &data);
714cb5b5c91SCorentin Chary
715cb5b5c91SCorentin Chary if (retval)
716cb5b5c91SCorentin Chary return retval;
717cb5b5c91SCorentin Chary
718cb5b5c91SCorentin Chary if (data.data[0] != 0 && data.data[0] != 1)
719cb5b5c91SCorentin Chary return -ENODEV;
720cb5b5c91SCorentin Chary
721cb5b5c91SCorentin Chary return data.data[0];
722cb5b5c91SCorentin Chary }
723cb5b5c91SCorentin Chary
write_battery_life_extender(struct samsung_laptop * samsung,int enabled)724cb5b5c91SCorentin Chary static int write_battery_life_extender(struct samsung_laptop *samsung,
725cb5b5c91SCorentin Chary int enabled)
726cb5b5c91SCorentin Chary {
727cb5b5c91SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
728cb5b5c91SCorentin Chary struct sabi_data data;
729cb5b5c91SCorentin Chary
730cb5b5c91SCorentin Chary memset(&data, 0, sizeof(data));
731cb5b5c91SCorentin Chary data.data[0] = 0x80 | enabled;
732cb5b5c91SCorentin Chary return sabi_command(samsung, commands->set_battery_life_extender,
733cb5b5c91SCorentin Chary &data, NULL);
734cb5b5c91SCorentin Chary }
735cb5b5c91SCorentin Chary
get_battery_life_extender(struct device * dev,struct device_attribute * attr,char * buf)736cb5b5c91SCorentin Chary static ssize_t get_battery_life_extender(struct device *dev,
737cb5b5c91SCorentin Chary struct device_attribute *attr,
738cb5b5c91SCorentin Chary char *buf)
739cb5b5c91SCorentin Chary {
740cb5b5c91SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
741cb5b5c91SCorentin Chary int ret;
742cb5b5c91SCorentin Chary
743cb5b5c91SCorentin Chary ret = read_battery_life_extender(samsung);
744cb5b5c91SCorentin Chary if (ret < 0)
745cb5b5c91SCorentin Chary return ret;
746cb5b5c91SCorentin Chary
747cb5b5c91SCorentin Chary return sprintf(buf, "%d\n", ret);
748cb5b5c91SCorentin Chary }
749cb5b5c91SCorentin Chary
set_battery_life_extender(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)750cb5b5c91SCorentin Chary static ssize_t set_battery_life_extender(struct device *dev,
751cb5b5c91SCorentin Chary struct device_attribute *attr,
752cb5b5c91SCorentin Chary const char *buf, size_t count)
753cb5b5c91SCorentin Chary {
754cb5b5c91SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
755cb5b5c91SCorentin Chary int ret, value;
756cb5b5c91SCorentin Chary
757802cf2e1SDarren Hart if (!count || kstrtoint(buf, 0, &value) != 0)
758cb5b5c91SCorentin Chary return -EINVAL;
759cb5b5c91SCorentin Chary
760cb5b5c91SCorentin Chary ret = write_battery_life_extender(samsung, !!value);
761cb5b5c91SCorentin Chary if (ret < 0)
762cb5b5c91SCorentin Chary return ret;
763cb5b5c91SCorentin Chary
764cb5b5c91SCorentin Chary return count;
765cb5b5c91SCorentin Chary }
766cb5b5c91SCorentin Chary
7675b6a9a2fSWolfram Sang static DEVICE_ATTR(battery_life_extender, 0644,
768cb5b5c91SCorentin Chary get_battery_life_extender, set_battery_life_extender);
769cb5b5c91SCorentin Chary
read_usb_charge(struct samsung_laptop * samsung)7703a75d378SCorentin Chary static int read_usb_charge(struct samsung_laptop *samsung)
7713a75d378SCorentin Chary {
7723a75d378SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
7733a75d378SCorentin Chary struct sabi_data data;
7743a75d378SCorentin Chary int retval;
7753a75d378SCorentin Chary
7763a75d378SCorentin Chary if (commands->get_usb_charge == 0xFFFF)
7773a75d378SCorentin Chary return -ENODEV;
7783a75d378SCorentin Chary
7793a75d378SCorentin Chary memset(&data, 0, sizeof(data));
7803a75d378SCorentin Chary data.data[0] = 0x80;
7813a75d378SCorentin Chary retval = sabi_command(samsung, commands->get_usb_charge,
7823a75d378SCorentin Chary &data, &data);
7833a75d378SCorentin Chary
7843a75d378SCorentin Chary if (retval)
7853a75d378SCorentin Chary return retval;
7863a75d378SCorentin Chary
7873a75d378SCorentin Chary if (data.data[0] != 0 && data.data[0] != 1)
7883a75d378SCorentin Chary return -ENODEV;
7893a75d378SCorentin Chary
7903a75d378SCorentin Chary return data.data[0];
7913a75d378SCorentin Chary }
7923a75d378SCorentin Chary
write_usb_charge(struct samsung_laptop * samsung,int enabled)7933a75d378SCorentin Chary static int write_usb_charge(struct samsung_laptop *samsung,
7943a75d378SCorentin Chary int enabled)
7953a75d378SCorentin Chary {
7963a75d378SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
7973a75d378SCorentin Chary struct sabi_data data;
7983a75d378SCorentin Chary
7993a75d378SCorentin Chary memset(&data, 0, sizeof(data));
8003a75d378SCorentin Chary data.data[0] = 0x80 | enabled;
8013a75d378SCorentin Chary return sabi_command(samsung, commands->set_usb_charge,
8023a75d378SCorentin Chary &data, NULL);
8033a75d378SCorentin Chary }
8043a75d378SCorentin Chary
get_usb_charge(struct device * dev,struct device_attribute * attr,char * buf)8053a75d378SCorentin Chary static ssize_t get_usb_charge(struct device *dev,
8063a75d378SCorentin Chary struct device_attribute *attr,
8073a75d378SCorentin Chary char *buf)
8083a75d378SCorentin Chary {
8093a75d378SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
8103a75d378SCorentin Chary int ret;
8113a75d378SCorentin Chary
8123a75d378SCorentin Chary ret = read_usb_charge(samsung);
8133a75d378SCorentin Chary if (ret < 0)
8143a75d378SCorentin Chary return ret;
8153a75d378SCorentin Chary
8163a75d378SCorentin Chary return sprintf(buf, "%d\n", ret);
8173a75d378SCorentin Chary }
8183a75d378SCorentin Chary
set_usb_charge(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)8193a75d378SCorentin Chary static ssize_t set_usb_charge(struct device *dev,
8203a75d378SCorentin Chary struct device_attribute *attr,
8213a75d378SCorentin Chary const char *buf, size_t count)
8223a75d378SCorentin Chary {
8233a75d378SCorentin Chary struct samsung_laptop *samsung = dev_get_drvdata(dev);
8243a75d378SCorentin Chary int ret, value;
8253a75d378SCorentin Chary
826802cf2e1SDarren Hart if (!count || kstrtoint(buf, 0, &value) != 0)
8273a75d378SCorentin Chary return -EINVAL;
8283a75d378SCorentin Chary
8293a75d378SCorentin Chary ret = write_usb_charge(samsung, !!value);
8303a75d378SCorentin Chary if (ret < 0)
8313a75d378SCorentin Chary return ret;
8323a75d378SCorentin Chary
8333a75d378SCorentin Chary return count;
8343a75d378SCorentin Chary }
8353a75d378SCorentin Chary
8365b6a9a2fSWolfram Sang static DEVICE_ATTR(usb_charge, 0644,
8373a75d378SCorentin Chary get_usb_charge, set_usb_charge);
8383a75d378SCorentin Chary
read_lid_handling(struct samsung_laptop * samsung)839b0dcaf4fSJulijonas Kikutis static int read_lid_handling(struct samsung_laptop *samsung)
840b0dcaf4fSJulijonas Kikutis {
841b0dcaf4fSJulijonas Kikutis const struct sabi_commands *commands = &samsung->config->commands;
842b0dcaf4fSJulijonas Kikutis struct sabi_data data;
843b0dcaf4fSJulijonas Kikutis int retval;
844b0dcaf4fSJulijonas Kikutis
845b0dcaf4fSJulijonas Kikutis if (commands->get_lid_handling == 0xFFFF)
846b0dcaf4fSJulijonas Kikutis return -ENODEV;
847b0dcaf4fSJulijonas Kikutis
848b0dcaf4fSJulijonas Kikutis memset(&data, 0, sizeof(data));
849b0dcaf4fSJulijonas Kikutis retval = sabi_command(samsung, commands->get_lid_handling,
850b0dcaf4fSJulijonas Kikutis &data, &data);
851b0dcaf4fSJulijonas Kikutis
852b0dcaf4fSJulijonas Kikutis if (retval)
853b0dcaf4fSJulijonas Kikutis return retval;
854b0dcaf4fSJulijonas Kikutis
855b0dcaf4fSJulijonas Kikutis return data.data[0] & 0x1;
856b0dcaf4fSJulijonas Kikutis }
857b0dcaf4fSJulijonas Kikutis
write_lid_handling(struct samsung_laptop * samsung,int enabled)858b0dcaf4fSJulijonas Kikutis static int write_lid_handling(struct samsung_laptop *samsung,
859b0dcaf4fSJulijonas Kikutis int enabled)
860b0dcaf4fSJulijonas Kikutis {
861b0dcaf4fSJulijonas Kikutis const struct sabi_commands *commands = &samsung->config->commands;
862b0dcaf4fSJulijonas Kikutis struct sabi_data data;
863b0dcaf4fSJulijonas Kikutis
864b0dcaf4fSJulijonas Kikutis memset(&data, 0, sizeof(data));
865b0dcaf4fSJulijonas Kikutis data.data[0] = 0x80 | enabled;
866b0dcaf4fSJulijonas Kikutis return sabi_command(samsung, commands->set_lid_handling,
867b0dcaf4fSJulijonas Kikutis &data, NULL);
868b0dcaf4fSJulijonas Kikutis }
869b0dcaf4fSJulijonas Kikutis
get_lid_handling(struct device * dev,struct device_attribute * attr,char * buf)870b0dcaf4fSJulijonas Kikutis static ssize_t get_lid_handling(struct device *dev,
871b0dcaf4fSJulijonas Kikutis struct device_attribute *attr,
872b0dcaf4fSJulijonas Kikutis char *buf)
873b0dcaf4fSJulijonas Kikutis {
874b0dcaf4fSJulijonas Kikutis struct samsung_laptop *samsung = dev_get_drvdata(dev);
875b0dcaf4fSJulijonas Kikutis int ret;
876b0dcaf4fSJulijonas Kikutis
877b0dcaf4fSJulijonas Kikutis ret = read_lid_handling(samsung);
878b0dcaf4fSJulijonas Kikutis if (ret < 0)
879b0dcaf4fSJulijonas Kikutis return ret;
880b0dcaf4fSJulijonas Kikutis
881b0dcaf4fSJulijonas Kikutis return sprintf(buf, "%d\n", ret);
882b0dcaf4fSJulijonas Kikutis }
883b0dcaf4fSJulijonas Kikutis
set_lid_handling(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)884b0dcaf4fSJulijonas Kikutis static ssize_t set_lid_handling(struct device *dev,
885b0dcaf4fSJulijonas Kikutis struct device_attribute *attr,
886b0dcaf4fSJulijonas Kikutis const char *buf, size_t count)
887b0dcaf4fSJulijonas Kikutis {
888b0dcaf4fSJulijonas Kikutis struct samsung_laptop *samsung = dev_get_drvdata(dev);
889b0dcaf4fSJulijonas Kikutis int ret, value;
890b0dcaf4fSJulijonas Kikutis
891b0dcaf4fSJulijonas Kikutis if (!count || kstrtoint(buf, 0, &value) != 0)
892b0dcaf4fSJulijonas Kikutis return -EINVAL;
893b0dcaf4fSJulijonas Kikutis
894b0dcaf4fSJulijonas Kikutis ret = write_lid_handling(samsung, !!value);
895b0dcaf4fSJulijonas Kikutis if (ret < 0)
896b0dcaf4fSJulijonas Kikutis return ret;
897b0dcaf4fSJulijonas Kikutis
898b0dcaf4fSJulijonas Kikutis return count;
899b0dcaf4fSJulijonas Kikutis }
900b0dcaf4fSJulijonas Kikutis
9015b6a9a2fSWolfram Sang static DEVICE_ATTR(lid_handling, 0644,
902b0dcaf4fSJulijonas Kikutis get_lid_handling, set_lid_handling);
903b0dcaf4fSJulijonas Kikutis
904a66c1662SCorentin Chary static struct attribute *platform_attributes[] = {
905a66c1662SCorentin Chary &dev_attr_performance_level.attr,
906cb5b5c91SCorentin Chary &dev_attr_battery_life_extender.attr,
9073a75d378SCorentin Chary &dev_attr_usb_charge.attr,
908b0dcaf4fSJulijonas Kikutis &dev_attr_lid_handling.attr,
909a66c1662SCorentin Chary NULL
910a66c1662SCorentin Chary };
9112d70b73aSGreg Kroah-Hartman
find_signature(void __iomem * memcheck,const char * testStr)9125dea7a20SCorentin Chary static int find_signature(void __iomem *memcheck, const char *testStr)
9135dea7a20SCorentin Chary {
9145dea7a20SCorentin Chary int i = 0;
9155dea7a20SCorentin Chary int loca;
9165dea7a20SCorentin Chary
9175dea7a20SCorentin Chary for (loca = 0; loca < 0xffff; loca++) {
9185dea7a20SCorentin Chary char temp = readb(memcheck + loca);
9195dea7a20SCorentin Chary
9205dea7a20SCorentin Chary if (temp == testStr[i]) {
9215dea7a20SCorentin Chary if (i == strlen(testStr)-1)
9225dea7a20SCorentin Chary break;
9235dea7a20SCorentin Chary ++i;
9245dea7a20SCorentin Chary } else {
9255dea7a20SCorentin Chary i = 0;
9265dea7a20SCorentin Chary }
9275dea7a20SCorentin Chary }
9285dea7a20SCorentin Chary return loca;
9295dea7a20SCorentin Chary }
9305dea7a20SCorentin Chary
samsung_rfkill_exit(struct samsung_laptop * samsung)9315dea7a20SCorentin Chary static void samsung_rfkill_exit(struct samsung_laptop *samsung)
9325dea7a20SCorentin Chary {
93384d482f2SCorentin Chary if (samsung->wlan.rfkill) {
93484d482f2SCorentin Chary rfkill_unregister(samsung->wlan.rfkill);
93584d482f2SCorentin Chary rfkill_destroy(samsung->wlan.rfkill);
93684d482f2SCorentin Chary samsung->wlan.rfkill = NULL;
9375dea7a20SCorentin Chary }
93884d482f2SCorentin Chary if (samsung->bluetooth.rfkill) {
93984d482f2SCorentin Chary rfkill_unregister(samsung->bluetooth.rfkill);
94084d482f2SCorentin Chary rfkill_destroy(samsung->bluetooth.rfkill);
94184d482f2SCorentin Chary samsung->bluetooth.rfkill = NULL;
94284d482f2SCorentin Chary }
94384d482f2SCorentin Chary }
94484d482f2SCorentin Chary
samsung_new_rfkill(struct samsung_laptop * samsung,struct samsung_rfkill * arfkill,const char * name,enum rfkill_type type,const struct rfkill_ops * ops,int blocked)94584d482f2SCorentin Chary static int samsung_new_rfkill(struct samsung_laptop *samsung,
94684d482f2SCorentin Chary struct samsung_rfkill *arfkill,
94784d482f2SCorentin Chary const char *name, enum rfkill_type type,
94884d482f2SCorentin Chary const struct rfkill_ops *ops,
94984d482f2SCorentin Chary int blocked)
95084d482f2SCorentin Chary {
95184d482f2SCorentin Chary struct rfkill **rfkill = &arfkill->rfkill;
95284d482f2SCorentin Chary int ret;
95384d482f2SCorentin Chary
95484d482f2SCorentin Chary arfkill->type = type;
95584d482f2SCorentin Chary arfkill->samsung = samsung;
95684d482f2SCorentin Chary
95784d482f2SCorentin Chary *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
95884d482f2SCorentin Chary type, ops, arfkill);
95984d482f2SCorentin Chary
96084d482f2SCorentin Chary if (!*rfkill)
96184d482f2SCorentin Chary return -EINVAL;
96284d482f2SCorentin Chary
96384d482f2SCorentin Chary if (blocked != -1)
96484d482f2SCorentin Chary rfkill_init_sw_state(*rfkill, blocked);
96584d482f2SCorentin Chary
96684d482f2SCorentin Chary ret = rfkill_register(*rfkill);
96784d482f2SCorentin Chary if (ret) {
96884d482f2SCorentin Chary rfkill_destroy(*rfkill);
96984d482f2SCorentin Chary *rfkill = NULL;
97084d482f2SCorentin Chary return ret;
97184d482f2SCorentin Chary }
97284d482f2SCorentin Chary return 0;
97384d482f2SCorentin Chary }
97484d482f2SCorentin Chary
samsung_rfkill_init_seclinux(struct samsung_laptop * samsung)97584d482f2SCorentin Chary static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
97684d482f2SCorentin Chary {
97784d482f2SCorentin Chary return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
97884d482f2SCorentin Chary RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
97984d482f2SCorentin Chary }
98084d482f2SCorentin Chary
samsung_rfkill_init_swsmi(struct samsung_laptop * samsung)98184d482f2SCorentin Chary static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
98284d482f2SCorentin Chary {
98384d482f2SCorentin Chary struct sabi_data data;
98484d482f2SCorentin Chary int ret;
98584d482f2SCorentin Chary
98684d482f2SCorentin Chary ret = swsmi_wireless_status(samsung, &data);
98720db88e3SCorentin Chary if (ret) {
98820db88e3SCorentin Chary /* Some swsmi laptops use the old seclinux way to control
98920db88e3SCorentin Chary * wireless devices */
99020db88e3SCorentin Chary if (ret == -EINVAL)
99120db88e3SCorentin Chary ret = samsung_rfkill_init_seclinux(samsung);
99284d482f2SCorentin Chary return ret;
99320db88e3SCorentin Chary }
99484d482f2SCorentin Chary
99584d482f2SCorentin Chary /* 0x02 seems to mean that the device is no present/available */
99684d482f2SCorentin Chary
99784d482f2SCorentin Chary if (data.data[WL_STATUS_WLAN] != 0x02)
99884d482f2SCorentin Chary ret = samsung_new_rfkill(samsung, &samsung->wlan,
99984d482f2SCorentin Chary "samsung-wlan",
100084d482f2SCorentin Chary RFKILL_TYPE_WLAN,
100184d482f2SCorentin Chary &swsmi_rfkill_ops,
100284d482f2SCorentin Chary !data.data[WL_STATUS_WLAN]);
100384d482f2SCorentin Chary if (ret)
100484d482f2SCorentin Chary goto exit;
100584d482f2SCorentin Chary
100684d482f2SCorentin Chary if (data.data[WL_STATUS_BT] != 0x02)
100784d482f2SCorentin Chary ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
100884d482f2SCorentin Chary "samsung-bluetooth",
100984d482f2SCorentin Chary RFKILL_TYPE_BLUETOOTH,
101084d482f2SCorentin Chary &swsmi_rfkill_ops,
101184d482f2SCorentin Chary !data.data[WL_STATUS_BT]);
101284d482f2SCorentin Chary if (ret)
101384d482f2SCorentin Chary goto exit;
101484d482f2SCorentin Chary
101584d482f2SCorentin Chary exit:
101684d482f2SCorentin Chary if (ret)
101784d482f2SCorentin Chary samsung_rfkill_exit(samsung);
101884d482f2SCorentin Chary
101984d482f2SCorentin Chary return ret;
10205dea7a20SCorentin Chary }
10215dea7a20SCorentin Chary
samsung_rfkill_init(struct samsung_laptop * samsung)10225dea7a20SCorentin Chary static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
10235dea7a20SCorentin Chary {
102484d482f2SCorentin Chary if (samsung->config->sabi_version == 2)
102584d482f2SCorentin Chary return samsung_rfkill_init_seclinux(samsung);
102684d482f2SCorentin Chary if (samsung->config->sabi_version == 3)
102784d482f2SCorentin Chary return samsung_rfkill_init_swsmi(samsung);
10285dea7a20SCorentin Chary return 0;
10295dea7a20SCorentin Chary }
10305dea7a20SCorentin Chary
samsung_lid_handling_exit(struct samsung_laptop * samsung)1031b0dcaf4fSJulijonas Kikutis static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
1032b0dcaf4fSJulijonas Kikutis {
1033b0dcaf4fSJulijonas Kikutis if (samsung->quirks->lid_handling)
1034b0dcaf4fSJulijonas Kikutis write_lid_handling(samsung, 0);
1035b0dcaf4fSJulijonas Kikutis }
1036b0dcaf4fSJulijonas Kikutis
samsung_lid_handling_init(struct samsung_laptop * samsung)1037b0dcaf4fSJulijonas Kikutis static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
1038b0dcaf4fSJulijonas Kikutis {
1039b0dcaf4fSJulijonas Kikutis int retval = 0;
1040b0dcaf4fSJulijonas Kikutis
1041b0dcaf4fSJulijonas Kikutis if (samsung->quirks->lid_handling)
1042b0dcaf4fSJulijonas Kikutis retval = write_lid_handling(samsung, 1);
1043b0dcaf4fSJulijonas Kikutis
1044b0dcaf4fSJulijonas Kikutis return retval;
1045b0dcaf4fSJulijonas Kikutis }
1046b0dcaf4fSJulijonas Kikutis
kbd_backlight_enable(struct samsung_laptop * samsung)1047f674ebf1SCorentin Chary static int kbd_backlight_enable(struct samsung_laptop *samsung)
1048f674ebf1SCorentin Chary {
1049f674ebf1SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
1050f674ebf1SCorentin Chary struct sabi_data data;
1051f674ebf1SCorentin Chary int retval;
1052f674ebf1SCorentin Chary
1053f674ebf1SCorentin Chary if (commands->kbd_backlight == 0xFFFF)
1054f674ebf1SCorentin Chary return -ENODEV;
1055f674ebf1SCorentin Chary
1056f674ebf1SCorentin Chary memset(&data, 0, sizeof(data));
1057f674ebf1SCorentin Chary data.d0 = 0xaabb;
1058f674ebf1SCorentin Chary retval = sabi_command(samsung, commands->kbd_backlight,
1059f674ebf1SCorentin Chary &data, &data);
1060f674ebf1SCorentin Chary
1061f674ebf1SCorentin Chary if (retval)
1062f674ebf1SCorentin Chary return retval;
1063f674ebf1SCorentin Chary
1064f674ebf1SCorentin Chary if (data.d0 != 0xccdd)
1065f674ebf1SCorentin Chary return -ENODEV;
1066f674ebf1SCorentin Chary return 0;
1067f674ebf1SCorentin Chary }
1068f674ebf1SCorentin Chary
kbd_backlight_read(struct samsung_laptop * samsung)1069f674ebf1SCorentin Chary static int kbd_backlight_read(struct samsung_laptop *samsung)
1070f674ebf1SCorentin Chary {
1071f674ebf1SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
1072f674ebf1SCorentin Chary struct sabi_data data;
1073f674ebf1SCorentin Chary int retval;
1074f674ebf1SCorentin Chary
1075f674ebf1SCorentin Chary memset(&data, 0, sizeof(data));
1076f674ebf1SCorentin Chary data.data[0] = 0x81;
1077f674ebf1SCorentin Chary retval = sabi_command(samsung, commands->kbd_backlight,
1078f674ebf1SCorentin Chary &data, &data);
1079f674ebf1SCorentin Chary
1080f674ebf1SCorentin Chary if (retval)
1081f674ebf1SCorentin Chary return retval;
1082f674ebf1SCorentin Chary
1083f674ebf1SCorentin Chary return data.data[0];
1084f674ebf1SCorentin Chary }
1085f674ebf1SCorentin Chary
kbd_backlight_write(struct samsung_laptop * samsung,int brightness)1086f674ebf1SCorentin Chary static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1087f674ebf1SCorentin Chary {
1088f674ebf1SCorentin Chary const struct sabi_commands *commands = &samsung->config->commands;
1089f674ebf1SCorentin Chary struct sabi_data data;
1090f674ebf1SCorentin Chary
1091f674ebf1SCorentin Chary memset(&data, 0, sizeof(data));
1092f674ebf1SCorentin Chary data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1093f674ebf1SCorentin Chary return sabi_command(samsung, commands->kbd_backlight,
1094f674ebf1SCorentin Chary &data, NULL);
1095f674ebf1SCorentin Chary }
1096f674ebf1SCorentin Chary
kbd_led_update(struct work_struct * work)1097f674ebf1SCorentin Chary static void kbd_led_update(struct work_struct *work)
1098f674ebf1SCorentin Chary {
1099f674ebf1SCorentin Chary struct samsung_laptop *samsung;
1100f674ebf1SCorentin Chary
1101f674ebf1SCorentin Chary samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1102f674ebf1SCorentin Chary kbd_backlight_write(samsung, samsung->kbd_led_wk);
1103f674ebf1SCorentin Chary }
1104f674ebf1SCorentin Chary
kbd_led_set(struct led_classdev * led_cdev,enum led_brightness value)1105f674ebf1SCorentin Chary static void kbd_led_set(struct led_classdev *led_cdev,
1106f674ebf1SCorentin Chary enum led_brightness value)
1107f674ebf1SCorentin Chary {
1108f674ebf1SCorentin Chary struct samsung_laptop *samsung;
1109f674ebf1SCorentin Chary
1110f674ebf1SCorentin Chary samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1111f674ebf1SCorentin Chary
1112f674ebf1SCorentin Chary if (value > samsung->kbd_led.max_brightness)
1113f674ebf1SCorentin Chary value = samsung->kbd_led.max_brightness;
1114f674ebf1SCorentin Chary
1115f674ebf1SCorentin Chary samsung->kbd_led_wk = value;
1116f674ebf1SCorentin Chary queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1117f674ebf1SCorentin Chary }
1118f674ebf1SCorentin Chary
kbd_led_get(struct led_classdev * led_cdev)1119f674ebf1SCorentin Chary static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1120f674ebf1SCorentin Chary {
1121f674ebf1SCorentin Chary struct samsung_laptop *samsung;
1122f674ebf1SCorentin Chary
1123f674ebf1SCorentin Chary samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1124f674ebf1SCorentin Chary return kbd_backlight_read(samsung);
1125f674ebf1SCorentin Chary }
1126f674ebf1SCorentin Chary
samsung_leds_exit(struct samsung_laptop * samsung)1127f674ebf1SCorentin Chary static void samsung_leds_exit(struct samsung_laptop *samsung)
1128f674ebf1SCorentin Chary {
1129f674ebf1SCorentin Chary led_classdev_unregister(&samsung->kbd_led);
1130f674ebf1SCorentin Chary if (samsung->led_workqueue)
1131f674ebf1SCorentin Chary destroy_workqueue(samsung->led_workqueue);
1132f674ebf1SCorentin Chary }
1133f674ebf1SCorentin Chary
samsung_leds_init(struct samsung_laptop * samsung)1134f674ebf1SCorentin Chary static int __init samsung_leds_init(struct samsung_laptop *samsung)
1135f674ebf1SCorentin Chary {
1136f674ebf1SCorentin Chary int ret = 0;
1137f674ebf1SCorentin Chary
1138f674ebf1SCorentin Chary samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1139f674ebf1SCorentin Chary if (!samsung->led_workqueue)
1140f674ebf1SCorentin Chary return -ENOMEM;
1141f674ebf1SCorentin Chary
1142f674ebf1SCorentin Chary if (kbd_backlight_enable(samsung) >= 0) {
1143f674ebf1SCorentin Chary INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1144f674ebf1SCorentin Chary
1145f674ebf1SCorentin Chary samsung->kbd_led.name = "samsung::kbd_backlight";
1146f674ebf1SCorentin Chary samsung->kbd_led.brightness_set = kbd_led_set;
1147f674ebf1SCorentin Chary samsung->kbd_led.brightness_get = kbd_led_get;
1148f674ebf1SCorentin Chary samsung->kbd_led.max_brightness = 8;
11490ca849eaSScott Thrasher if (samsung->quirks->four_kbd_backlight_levels)
11500ca849eaSScott Thrasher samsung->kbd_led.max_brightness = 4;
1151f674ebf1SCorentin Chary
1152f674ebf1SCorentin Chary ret = led_classdev_register(&samsung->platform_device->dev,
1153f674ebf1SCorentin Chary &samsung->kbd_led);
1154f674ebf1SCorentin Chary }
1155f674ebf1SCorentin Chary
1156f674ebf1SCorentin Chary if (ret)
1157f674ebf1SCorentin Chary samsung_leds_exit(samsung);
1158f674ebf1SCorentin Chary
1159f674ebf1SCorentin Chary return ret;
1160f674ebf1SCorentin Chary }
1161f674ebf1SCorentin Chary
samsung_backlight_exit(struct samsung_laptop * samsung)11625dea7a20SCorentin Chary static void samsung_backlight_exit(struct samsung_laptop *samsung)
11635dea7a20SCorentin Chary {
11645dea7a20SCorentin Chary if (samsung->backlight_device) {
11655dea7a20SCorentin Chary backlight_device_unregister(samsung->backlight_device);
11665dea7a20SCorentin Chary samsung->backlight_device = NULL;
11675dea7a20SCorentin Chary }
11685dea7a20SCorentin Chary }
11695dea7a20SCorentin Chary
samsung_backlight_init(struct samsung_laptop * samsung)11705dea7a20SCorentin Chary static int __init samsung_backlight_init(struct samsung_laptop *samsung)
11715dea7a20SCorentin Chary {
11725dea7a20SCorentin Chary struct backlight_device *bd;
11735dea7a20SCorentin Chary struct backlight_properties props;
11745dea7a20SCorentin Chary
1175f34cd9caSCorentin Chary if (!samsung->handle_backlight)
1176f34cd9caSCorentin Chary return 0;
1177f34cd9caSCorentin Chary
11785dea7a20SCorentin Chary memset(&props, 0, sizeof(struct backlight_properties));
11795dea7a20SCorentin Chary props.type = BACKLIGHT_PLATFORM;
11805dea7a20SCorentin Chary props.max_brightness = samsung->config->max_brightness -
11815dea7a20SCorentin Chary samsung->config->min_brightness;
11825dea7a20SCorentin Chary
11835dea7a20SCorentin Chary bd = backlight_device_register("samsung",
11845dea7a20SCorentin Chary &samsung->platform_device->dev,
11855dea7a20SCorentin Chary samsung, &backlight_ops,
11865dea7a20SCorentin Chary &props);
11875dea7a20SCorentin Chary if (IS_ERR(bd))
11885dea7a20SCorentin Chary return PTR_ERR(bd);
11895dea7a20SCorentin Chary
11905dea7a20SCorentin Chary samsung->backlight_device = bd;
11915dea7a20SCorentin Chary samsung->backlight_device->props.brightness = read_brightness(samsung);
11925dea7a20SCorentin Chary samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
11935dea7a20SCorentin Chary backlight_update_status(samsung->backlight_device);
11945dea7a20SCorentin Chary
11955dea7a20SCorentin Chary return 0;
11965dea7a20SCorentin Chary }
11975dea7a20SCorentin Chary
samsung_sysfs_is_visible(struct kobject * kobj,struct attribute * attr,int idx)1198bde9e509SDan Carpenter static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1199a66c1662SCorentin Chary struct attribute *attr, int idx)
1200a66c1662SCorentin Chary {
1201c8ad6a76SMinghao Chi struct device *dev = kobj_to_dev(kobj);
1202d605ca29SWolfram Sang struct samsung_laptop *samsung = dev_get_drvdata(dev);
1203a66c1662SCorentin Chary bool ok = true;
1204a66c1662SCorentin Chary
1205a66c1662SCorentin Chary if (attr == &dev_attr_performance_level.attr)
1206a66c1662SCorentin Chary ok = !!samsung->config->performance_levels[0].name;
1207cb5b5c91SCorentin Chary if (attr == &dev_attr_battery_life_extender.attr)
1208cb5b5c91SCorentin Chary ok = !!(read_battery_life_extender(samsung) >= 0);
12093a75d378SCorentin Chary if (attr == &dev_attr_usb_charge.attr)
12103a75d378SCorentin Chary ok = !!(read_usb_charge(samsung) >= 0);
1211b0dcaf4fSJulijonas Kikutis if (attr == &dev_attr_lid_handling.attr)
1212b0dcaf4fSJulijonas Kikutis ok = !!(read_lid_handling(samsung) >= 0);
1213a66c1662SCorentin Chary
1214a66c1662SCorentin Chary return ok ? attr->mode : 0;
1215a66c1662SCorentin Chary }
1216a66c1662SCorentin Chary
12178546268eSArvind Yadav static const struct attribute_group platform_attribute_group = {
1218a66c1662SCorentin Chary .is_visible = samsung_sysfs_is_visible,
1219a66c1662SCorentin Chary .attrs = platform_attributes
1220a66c1662SCorentin Chary };
1221a66c1662SCorentin Chary
samsung_sysfs_exit(struct samsung_laptop * samsung)12225dea7a20SCorentin Chary static void samsung_sysfs_exit(struct samsung_laptop *samsung)
12235dea7a20SCorentin Chary {
1224a66c1662SCorentin Chary struct platform_device *device = samsung->platform_device;
1225a66c1662SCorentin Chary
1226a66c1662SCorentin Chary sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
12275dea7a20SCorentin Chary }
12285dea7a20SCorentin Chary
samsung_sysfs_init(struct samsung_laptop * samsung)12295dea7a20SCorentin Chary static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
12305dea7a20SCorentin Chary {
1231a66c1662SCorentin Chary struct platform_device *device = samsung->platform_device;
1232a66c1662SCorentin Chary
1233a66c1662SCorentin Chary return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1234a66c1662SCorentin Chary
12355dea7a20SCorentin Chary }
12365dea7a20SCorentin Chary
samsung_laptop_call_show(struct seq_file * m,void * data)12379a7c551bSAndy Shevchenko static int samsung_laptop_call_show(struct seq_file *m, void *data)
12385b80fc40SCorentin Chary {
12395b80fc40SCorentin Chary struct samsung_laptop *samsung = m->private;
12405b80fc40SCorentin Chary struct sabi_data *sdata = &samsung->debug.data;
12415b80fc40SCorentin Chary int ret;
12425b80fc40SCorentin Chary
12435b80fc40SCorentin Chary seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
12445b80fc40SCorentin Chary samsung->debug.command,
12455b80fc40SCorentin Chary sdata->d0, sdata->d1, sdata->d2, sdata->d3);
12465b80fc40SCorentin Chary
12475b80fc40SCorentin Chary ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
12485b80fc40SCorentin Chary
12495b80fc40SCorentin Chary if (ret) {
12505b80fc40SCorentin Chary seq_printf(m, "SABI command 0x%04x failed\n",
12515b80fc40SCorentin Chary samsung->debug.command);
12525b80fc40SCorentin Chary return ret;
12535b80fc40SCorentin Chary }
12545b80fc40SCorentin Chary
12555b80fc40SCorentin Chary seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
12565b80fc40SCorentin Chary sdata->d0, sdata->d1, sdata->d2, sdata->d3);
12575b80fc40SCorentin Chary return 0;
12585b80fc40SCorentin Chary }
12599a7c551bSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(samsung_laptop_call);
12605b80fc40SCorentin Chary
samsung_debugfs_exit(struct samsung_laptop * samsung)12615b80fc40SCorentin Chary static void samsung_debugfs_exit(struct samsung_laptop *samsung)
12625b80fc40SCorentin Chary {
12635b80fc40SCorentin Chary debugfs_remove_recursive(samsung->debug.root);
12645b80fc40SCorentin Chary }
12655b80fc40SCorentin Chary
samsung_debugfs_init(struct samsung_laptop * samsung)1266d30cdc9aSGreg Kroah-Hartman static void samsung_debugfs_init(struct samsung_laptop *samsung)
12675b80fc40SCorentin Chary {
1268d30cdc9aSGreg Kroah-Hartman struct dentry *root;
12695b80fc40SCorentin Chary
1270d30cdc9aSGreg Kroah-Hartman root = debugfs_create_dir("samsung-laptop", NULL);
1271d30cdc9aSGreg Kroah-Hartman samsung->debug.root = root;
12725b80fc40SCorentin Chary
12735b80fc40SCorentin Chary samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
12745b80fc40SCorentin Chary samsung->debug.f0000_wrapper.size = 0xffff;
12755b80fc40SCorentin Chary
12765b80fc40SCorentin Chary samsung->debug.data_wrapper.data = &samsung->debug.data;
12775b80fc40SCorentin Chary samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
12785b80fc40SCorentin Chary
12796f6ae06eSCorentin Chary samsung->debug.sdiag_wrapper.data = samsung->sdiag;
12806f6ae06eSCorentin Chary samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
12816f6ae06eSCorentin Chary
12825b6a9a2fSWolfram Sang debugfs_create_u16("command", 0644, root, &samsung->debug.command);
12835b6a9a2fSWolfram Sang debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0);
12845b6a9a2fSWolfram Sang debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1);
12855b6a9a2fSWolfram Sang debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2);
12865b6a9a2fSWolfram Sang debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3);
12871351f1d1SWolfram Sang debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper);
12881351f1d1SWolfram Sang debugfs_create_blob("f0000_segment", 0400, root,
12895b80fc40SCorentin Chary &samsung->debug.f0000_wrapper);
12901351f1d1SWolfram Sang debugfs_create_file("call", 0444, root, samsung,
12919a7c551bSAndy Shevchenko &samsung_laptop_call_fops);
12921351f1d1SWolfram Sang debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper);
12935b80fc40SCorentin Chary }
12945b80fc40SCorentin Chary
samsung_sabi_exit(struct samsung_laptop * samsung)12955dea7a20SCorentin Chary static void samsung_sabi_exit(struct samsung_laptop *samsung)
12965dea7a20SCorentin Chary {
12975dea7a20SCorentin Chary const struct sabi_config *config = samsung->config;
12985dea7a20SCorentin Chary
12995dea7a20SCorentin Chary /* Turn off "Linux" mode in the BIOS */
13005dea7a20SCorentin Chary if (config && config->commands.set_linux != 0xff)
13017e960711SCorentin Chary sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
13025dea7a20SCorentin Chary
13035dea7a20SCorentin Chary if (samsung->sabi_iface) {
13045dea7a20SCorentin Chary iounmap(samsung->sabi_iface);
13055dea7a20SCorentin Chary samsung->sabi_iface = NULL;
13065dea7a20SCorentin Chary }
13075dea7a20SCorentin Chary if (samsung->f0000_segment) {
13085dea7a20SCorentin Chary iounmap(samsung->f0000_segment);
13095dea7a20SCorentin Chary samsung->f0000_segment = NULL;
13105dea7a20SCorentin Chary }
13115dea7a20SCorentin Chary
13125dea7a20SCorentin Chary samsung->config = NULL;
13135dea7a20SCorentin Chary }
13145dea7a20SCorentin Chary
samsung_sabi_infos(struct samsung_laptop * samsung,int loca,unsigned int ifaceP)131549dd7730SCorentin Chary static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
131649dd7730SCorentin Chary unsigned int ifaceP)
13175dea7a20SCorentin Chary {
13185dea7a20SCorentin Chary const struct sabi_config *config = samsung->config;
13195dea7a20SCorentin Chary
13205dea7a20SCorentin Chary printk(KERN_DEBUG "This computer supports SABI==%x\n",
13215dea7a20SCorentin Chary loca + 0xf0000 - 6);
132249dd7730SCorentin Chary
13235dea7a20SCorentin Chary printk(KERN_DEBUG "SABI header:\n");
13245dea7a20SCorentin Chary printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
13255dea7a20SCorentin Chary readw(samsung->sabi + config->header_offsets.port));
13265dea7a20SCorentin Chary printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
13275dea7a20SCorentin Chary readb(samsung->sabi + config->header_offsets.iface_func));
13285dea7a20SCorentin Chary printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
13295dea7a20SCorentin Chary readb(samsung->sabi + config->header_offsets.en_mem));
13305dea7a20SCorentin Chary printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
13315dea7a20SCorentin Chary readb(samsung->sabi + config->header_offsets.re_mem));
13325dea7a20SCorentin Chary printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
13335dea7a20SCorentin Chary readw(samsung->sabi + config->header_offsets.data_offset));
13345dea7a20SCorentin Chary printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
13355dea7a20SCorentin Chary readw(samsung->sabi + config->header_offsets.data_segment));
13365dea7a20SCorentin Chary
133749dd7730SCorentin Chary printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
13385dea7a20SCorentin Chary }
13395dea7a20SCorentin Chary
samsung_sabi_diag(struct samsung_laptop * samsung)13406f6ae06eSCorentin Chary static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
13416f6ae06eSCorentin Chary {
13426f6ae06eSCorentin Chary int loca = find_signature(samsung->f0000_segment, "SDiaG@");
13436f6ae06eSCorentin Chary int i;
13446f6ae06eSCorentin Chary
13456f6ae06eSCorentin Chary if (loca == 0xffff)
13466f6ae06eSCorentin Chary return ;
13476f6ae06eSCorentin Chary
13486f6ae06eSCorentin Chary /* Example:
13496f6ae06eSCorentin Chary * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
13506f6ae06eSCorentin Chary *
13516f6ae06eSCorentin Chary * Product name: 90X3A
13526f6ae06eSCorentin Chary * BIOS Version: 07HL
13536f6ae06eSCorentin Chary */
13546f6ae06eSCorentin Chary loca += 1;
13556f6ae06eSCorentin Chary for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
13566f6ae06eSCorentin Chary char temp = readb(samsung->f0000_segment + loca);
13576f6ae06eSCorentin Chary
13586f6ae06eSCorentin Chary if (isalnum(temp) || temp == '/' || temp == '-')
13596f6ae06eSCorentin Chary samsung->sdiag[i++] = temp;
13606f6ae06eSCorentin Chary else
13616f6ae06eSCorentin Chary break ;
13626f6ae06eSCorentin Chary }
13636f6ae06eSCorentin Chary
13646f6ae06eSCorentin Chary if (debug && samsung->sdiag[0])
13656f6ae06eSCorentin Chary pr_info("sdiag: %s", samsung->sdiag);
13666f6ae06eSCorentin Chary }
13676f6ae06eSCorentin Chary
samsung_sabi_init(struct samsung_laptop * samsung)13685dea7a20SCorentin Chary static int __init samsung_sabi_init(struct samsung_laptop *samsung)
13695dea7a20SCorentin Chary {
13705dea7a20SCorentin Chary const struct sabi_config *config = NULL;
13715dea7a20SCorentin Chary const struct sabi_commands *commands;
13725dea7a20SCorentin Chary unsigned int ifaceP;
13730d2c9535SAndy Shevchenko int loca = 0xffff;
13745dea7a20SCorentin Chary int ret = 0;
13755dea7a20SCorentin Chary int i;
13765dea7a20SCorentin Chary
13774bdc0d67SChristoph Hellwig samsung->f0000_segment = ioremap(0xf0000, 0xffff);
13785dea7a20SCorentin Chary if (!samsung->f0000_segment) {
13793be324a9SCorentin Chary if (debug || force)
13805dea7a20SCorentin Chary pr_err("Can't map the segment at 0xf0000\n");
13815dea7a20SCorentin Chary ret = -EINVAL;
13825dea7a20SCorentin Chary goto exit;
13835dea7a20SCorentin Chary }
13845dea7a20SCorentin Chary
13856f6ae06eSCorentin Chary samsung_sabi_diag(samsung);
13866f6ae06eSCorentin Chary
13875dea7a20SCorentin Chary /* Try to find one of the signatures in memory to find the header */
13884e7f09adSLad, Prabhakar for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
13895dea7a20SCorentin Chary samsung->config = &sabi_configs[i];
13905dea7a20SCorentin Chary loca = find_signature(samsung->f0000_segment,
13915dea7a20SCorentin Chary samsung->config->test_string);
13925dea7a20SCorentin Chary if (loca != 0xffff)
13935dea7a20SCorentin Chary break;
13945dea7a20SCorentin Chary }
13955dea7a20SCorentin Chary
13965dea7a20SCorentin Chary if (loca == 0xffff) {
13973be324a9SCorentin Chary if (debug || force)
13985dea7a20SCorentin Chary pr_err("This computer does not support SABI\n");
13995dea7a20SCorentin Chary ret = -ENODEV;
14005dea7a20SCorentin Chary goto exit;
14015dea7a20SCorentin Chary }
14025dea7a20SCorentin Chary
14035dea7a20SCorentin Chary config = samsung->config;
14045dea7a20SCorentin Chary commands = &config->commands;
14055dea7a20SCorentin Chary
14065dea7a20SCorentin Chary /* point to the SMI port Number */
14075dea7a20SCorentin Chary loca += 1;
14085dea7a20SCorentin Chary samsung->sabi = (samsung->f0000_segment + loca);
14095dea7a20SCorentin Chary
14105dea7a20SCorentin Chary /* Get a pointer to the SABI Interface */
14115dea7a20SCorentin Chary ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
14125dea7a20SCorentin Chary ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
141349dd7730SCorentin Chary
141449dd7730SCorentin Chary if (debug)
141549dd7730SCorentin Chary samsung_sabi_infos(samsung, loca, ifaceP);
141649dd7730SCorentin Chary
14174bdc0d67SChristoph Hellwig samsung->sabi_iface = ioremap(ifaceP, 16);
14185dea7a20SCorentin Chary if (!samsung->sabi_iface) {
14195dea7a20SCorentin Chary pr_err("Can't remap %x\n", ifaceP);
14205dea7a20SCorentin Chary ret = -EINVAL;
14215dea7a20SCorentin Chary goto exit;
14225dea7a20SCorentin Chary }
14235dea7a20SCorentin Chary
14245dea7a20SCorentin Chary /* Turn on "Linux" mode in the BIOS */
14255dea7a20SCorentin Chary if (commands->set_linux != 0xff) {
14267e960711SCorentin Chary int retval = sabi_set_commandb(samsung,
14275dea7a20SCorentin Chary commands->set_linux, 0x81);
14285dea7a20SCorentin Chary if (retval) {
14295dea7a20SCorentin Chary pr_warn("Linux mode was not set!\n");
14305dea7a20SCorentin Chary ret = -ENODEV;
14315dea7a20SCorentin Chary goto exit;
14325dea7a20SCorentin Chary }
14335dea7a20SCorentin Chary }
14345dea7a20SCorentin Chary
14355dea7a20SCorentin Chary /* Check for stepping quirk */
1436f34cd9caSCorentin Chary if (samsung->handle_backlight)
14375dea7a20SCorentin Chary check_for_stepping_quirk(samsung);
14385dea7a20SCorentin Chary
14392e777187SCorentin Chary pr_info("detected SABI interface: %s\n",
14402e777187SCorentin Chary samsung->config->test_string);
14412e777187SCorentin Chary
14425dea7a20SCorentin Chary exit:
14435dea7a20SCorentin Chary if (ret)
14445dea7a20SCorentin Chary samsung_sabi_exit(samsung);
14455dea7a20SCorentin Chary
14465dea7a20SCorentin Chary return ret;
14475dea7a20SCorentin Chary }
14485dea7a20SCorentin Chary
samsung_platform_exit(struct samsung_laptop * samsung)14495dea7a20SCorentin Chary static void samsung_platform_exit(struct samsung_laptop *samsung)
14505dea7a20SCorentin Chary {
14515dea7a20SCorentin Chary if (samsung->platform_device) {
14525dea7a20SCorentin Chary platform_device_unregister(samsung->platform_device);
14535dea7a20SCorentin Chary samsung->platform_device = NULL;
14545dea7a20SCorentin Chary }
14555dea7a20SCorentin Chary }
14565dea7a20SCorentin Chary
samsung_pm_notification(struct notifier_block * nb,unsigned long val,void * ptr)14570ca849eaSScott Thrasher static int samsung_pm_notification(struct notifier_block *nb,
14580ca849eaSScott Thrasher unsigned long val, void *ptr)
14590ca849eaSScott Thrasher {
14600ca849eaSScott Thrasher struct samsung_laptop *samsung;
14610ca849eaSScott Thrasher
14620ca849eaSScott Thrasher samsung = container_of(nb, struct samsung_laptop, pm_nb);
14630ca849eaSScott Thrasher if (val == PM_POST_HIBERNATION &&
14640ca849eaSScott Thrasher samsung->quirks->enable_kbd_backlight)
14650ca849eaSScott Thrasher kbd_backlight_enable(samsung);
14660ca849eaSScott Thrasher
1467b0dcaf4fSJulijonas Kikutis if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
1468b0dcaf4fSJulijonas Kikutis write_lid_handling(samsung, 1);
1469b0dcaf4fSJulijonas Kikutis
14700ca849eaSScott Thrasher return 0;
14710ca849eaSScott Thrasher }
14720ca849eaSScott Thrasher
samsung_platform_init(struct samsung_laptop * samsung)14735dea7a20SCorentin Chary static int __init samsung_platform_init(struct samsung_laptop *samsung)
14745dea7a20SCorentin Chary {
14755dea7a20SCorentin Chary struct platform_device *pdev;
14765dea7a20SCorentin Chary
1477*8d05fc03SBarnabás Pőcze pdev = platform_device_register_simple("samsung", PLATFORM_DEVID_NONE, NULL, 0);
14785dea7a20SCorentin Chary if (IS_ERR(pdev))
14795dea7a20SCorentin Chary return PTR_ERR(pdev);
14805dea7a20SCorentin Chary
14815dea7a20SCorentin Chary samsung->platform_device = pdev;
14825dea7a20SCorentin Chary platform_set_drvdata(samsung->platform_device, samsung);
14835dea7a20SCorentin Chary return 0;
14845dea7a20SCorentin Chary }
14855dea7a20SCorentin Chary
1486a979e2e2SCorentin Chary static struct samsung_quirks *quirks;
1487a979e2e2SCorentin Chary
samsung_dmi_matched(const struct dmi_system_id * d)1488a979e2e2SCorentin Chary static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1489a979e2e2SCorentin Chary {
1490a979e2e2SCorentin Chary quirks = d->driver_data;
1491a979e2e2SCorentin Chary return 0;
1492a979e2e2SCorentin Chary }
1493a979e2e2SCorentin Chary
14946faadbbbSChristoph Hellwig static const struct dmi_system_id samsung_dmi_table[] __initconst = {
14952d70b73aSGreg Kroah-Hartman {
14962d70b73aSGreg Kroah-Hartman .matches = {
14972d70b73aSGreg Kroah-Hartman DMI_MATCH(DMI_SYS_VENDOR,
14982d70b73aSGreg Kroah-Hartman "SAMSUNG ELECTRONICS CO., LTD."),
14993be324a9SCorentin Chary DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
15002d70b73aSGreg Kroah-Hartman },
15012d70b73aSGreg Kroah-Hartman },
15022d70b73aSGreg Kroah-Hartman {
15032d70b73aSGreg Kroah-Hartman .matches = {
15042d70b73aSGreg Kroah-Hartman DMI_MATCH(DMI_SYS_VENDOR,
15052d70b73aSGreg Kroah-Hartman "SAMSUNG ELECTRONICS CO., LTD."),
15063be324a9SCorentin Chary DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
15072d70b73aSGreg Kroah-Hartman },
15082d70b73aSGreg Kroah-Hartman },
15092d70b73aSGreg Kroah-Hartman {
15104e2441c0SJ Witteveen .matches = {
15114e2441c0SJ Witteveen DMI_MATCH(DMI_SYS_VENDOR,
15124e2441c0SJ Witteveen "SAMSUNG ELECTRONICS CO., LTD."),
15133be324a9SCorentin Chary DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
15144e2441c0SJ Witteveen },
15154e2441c0SJ Witteveen },
15164e2441c0SJ Witteveen {
15172d70b73aSGreg Kroah-Hartman .matches = {
15182d70b73aSGreg Kroah-Hartman DMI_MATCH(DMI_SYS_VENDOR,
15192d70b73aSGreg Kroah-Hartman "SAMSUNG ELECTRONICS CO., LTD."),
15203be324a9SCorentin Chary DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
15212d70b73aSGreg Kroah-Hartman },
15227500eeb0STommaso Massimi },
1523e052067dSCorentin Chary /* DMI ids for laptops with bad Chassis Type */
1524e052067dSCorentin Chary {
1525e052067dSCorentin Chary .ident = "R40/R41",
1526e052067dSCorentin Chary .matches = {
1527e052067dSCorentin Chary DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1528e052067dSCorentin Chary DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1529e052067dSCorentin Chary DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1530e052067dSCorentin Chary },
1531e052067dSCorentin Chary },
1532a979e2e2SCorentin Chary /* Specific DMI ids for laptop with quirks */
1533a979e2e2SCorentin Chary {
1534a979e2e2SCorentin Chary .callback = samsung_dmi_matched,
15350ca849eaSScott Thrasher .ident = "730U3E/740U3E",
15360ca849eaSScott Thrasher .matches = {
15370ca849eaSScott Thrasher DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
15380ca849eaSScott Thrasher DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
15390ca849eaSScott Thrasher },
15400ca849eaSScott Thrasher .driver_data = &samsung_np740u3e,
15410ca849eaSScott Thrasher },
1542b0dcaf4fSJulijonas Kikutis {
1543b0dcaf4fSJulijonas Kikutis .callback = samsung_dmi_matched,
1544b0dcaf4fSJulijonas Kikutis .ident = "300V3Z/300V4Z/300V5Z",
1545b0dcaf4fSJulijonas Kikutis .matches = {
1546b0dcaf4fSJulijonas Kikutis DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1547b0dcaf4fSJulijonas Kikutis DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
1548b0dcaf4fSJulijonas Kikutis },
1549b0dcaf4fSJulijonas Kikutis .driver_data = &samsung_lid_handling,
1550b0dcaf4fSJulijonas Kikutis },
15512d70b73aSGreg Kroah-Hartman { },
15522d70b73aSGreg Kroah-Hartman };
15532d70b73aSGreg Kroah-Hartman MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
15542d70b73aSGreg Kroah-Hartman
15555dea7a20SCorentin Chary static struct platform_device *samsung_platform_device;
15562d70b73aSGreg Kroah-Hartman
samsung_init(void)15572d70b73aSGreg Kroah-Hartman static int __init samsung_init(void)
15582d70b73aSGreg Kroah-Hartman {
15595dea7a20SCorentin Chary struct samsung_laptop *samsung;
15605dea7a20SCorentin Chary int ret;
15612d70b73aSGreg Kroah-Hartman
1562e0094244SMatt Fleming if (efi_enabled(EFI_BOOT))
1563e0094244SMatt Fleming return -ENODEV;
1564e0094244SMatt Fleming
1565a979e2e2SCorentin Chary quirks = &samsung_unknown;
15662d70b73aSGreg Kroah-Hartman if (!force && !dmi_check_system(samsung_dmi_table))
15672d70b73aSGreg Kroah-Hartman return -ENODEV;
15682d70b73aSGreg Kroah-Hartman
1569a6df4894SCorentin Chary samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1570a6df4894SCorentin Chary if (!samsung)
1571a6df4894SCorentin Chary return -ENOMEM;
1572a6df4894SCorentin Chary
1573a6df4894SCorentin Chary mutex_init(&samsung->sabi_mutex);
1574f34cd9caSCorentin Chary samsung->handle_backlight = true;
1575a979e2e2SCorentin Chary samsung->quirks = quirks;
1576f34cd9caSCorentin Chary
15776c072299SHans de Goede if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
1578f34cd9caSCorentin Chary samsung->handle_backlight = false;
1579a979e2e2SCorentin Chary
15805dea7a20SCorentin Chary ret = samsung_platform_init(samsung);
15815dea7a20SCorentin Chary if (ret)
15825dea7a20SCorentin Chary goto error_platform;
15832d70b73aSGreg Kroah-Hartman
15845dea7a20SCorentin Chary ret = samsung_sabi_init(samsung);
15855dea7a20SCorentin Chary if (ret)
15865dea7a20SCorentin Chary goto error_sabi;
15872d70b73aSGreg Kroah-Hartman
15885dea7a20SCorentin Chary ret = samsung_sysfs_init(samsung);
15895dea7a20SCorentin Chary if (ret)
15905dea7a20SCorentin Chary goto error_sysfs;
15912d70b73aSGreg Kroah-Hartman
15925dea7a20SCorentin Chary ret = samsung_backlight_init(samsung);
15935dea7a20SCorentin Chary if (ret)
15945dea7a20SCorentin Chary goto error_backlight;
1595a6df4894SCorentin Chary
15965dea7a20SCorentin Chary ret = samsung_rfkill_init(samsung);
15975dea7a20SCorentin Chary if (ret)
15985dea7a20SCorentin Chary goto error_rfkill;
15992d70b73aSGreg Kroah-Hartman
1600f674ebf1SCorentin Chary ret = samsung_leds_init(samsung);
1601f674ebf1SCorentin Chary if (ret)
1602f674ebf1SCorentin Chary goto error_leds;
1603f674ebf1SCorentin Chary
1604b0dcaf4fSJulijonas Kikutis ret = samsung_lid_handling_init(samsung);
1605b0dcaf4fSJulijonas Kikutis if (ret)
1606b0dcaf4fSJulijonas Kikutis goto error_lid_handling;
1607b0dcaf4fSJulijonas Kikutis
1608d30cdc9aSGreg Kroah-Hartman samsung_debugfs_init(samsung);
16095b80fc40SCorentin Chary
16100ca849eaSScott Thrasher samsung->pm_nb.notifier_call = samsung_pm_notification;
16110ca849eaSScott Thrasher register_pm_notifier(&samsung->pm_nb);
16120ca849eaSScott Thrasher
16135dea7a20SCorentin Chary samsung_platform_device = samsung->platform_device;
16145dea7a20SCorentin Chary return ret;
16152d70b73aSGreg Kroah-Hartman
1616b0dcaf4fSJulijonas Kikutis error_lid_handling:
1617f674ebf1SCorentin Chary samsung_leds_exit(samsung);
1618f674ebf1SCorentin Chary error_leds:
16195b80fc40SCorentin Chary samsung_rfkill_exit(samsung);
16205dea7a20SCorentin Chary error_rfkill:
16215dea7a20SCorentin Chary samsung_backlight_exit(samsung);
16225dea7a20SCorentin Chary error_backlight:
16235dea7a20SCorentin Chary samsung_sysfs_exit(samsung);
16245dea7a20SCorentin Chary error_sysfs:
16255dea7a20SCorentin Chary samsung_sabi_exit(samsung);
16265dea7a20SCorentin Chary error_sabi:
16275dea7a20SCorentin Chary samsung_platform_exit(samsung);
16285dea7a20SCorentin Chary error_platform:
1629a6df4894SCorentin Chary kfree(samsung);
16305dea7a20SCorentin Chary return ret;
16312d70b73aSGreg Kroah-Hartman }
16322d70b73aSGreg Kroah-Hartman
samsung_exit(void)16332d70b73aSGreg Kroah-Hartman static void __exit samsung_exit(void)
16342d70b73aSGreg Kroah-Hartman {
16355dea7a20SCorentin Chary struct samsung_laptop *samsung;
16362d70b73aSGreg Kroah-Hartman
16375dea7a20SCorentin Chary samsung = platform_get_drvdata(samsung_platform_device);
16380ca849eaSScott Thrasher unregister_pm_notifier(&samsung->pm_nb);
1639a6df4894SCorentin Chary
16405b80fc40SCorentin Chary samsung_debugfs_exit(samsung);
1641b0dcaf4fSJulijonas Kikutis samsung_lid_handling_exit(samsung);
1642f674ebf1SCorentin Chary samsung_leds_exit(samsung);
16435dea7a20SCorentin Chary samsung_rfkill_exit(samsung);
16445dea7a20SCorentin Chary samsung_backlight_exit(samsung);
16455dea7a20SCorentin Chary samsung_sysfs_exit(samsung);
16465dea7a20SCorentin Chary samsung_sabi_exit(samsung);
16475dea7a20SCorentin Chary samsung_platform_exit(samsung);
16485dea7a20SCorentin Chary
1649a6df4894SCorentin Chary kfree(samsung);
16505dea7a20SCorentin Chary samsung_platform_device = NULL;
16512d70b73aSGreg Kroah-Hartman }
16522d70b73aSGreg Kroah-Hartman
16532d70b73aSGreg Kroah-Hartman module_init(samsung_init);
16542d70b73aSGreg Kroah-Hartman module_exit(samsung_exit);
16552d70b73aSGreg Kroah-Hartman
16562d70b73aSGreg Kroah-Hartman MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
16572d70b73aSGreg Kroah-Hartman MODULE_DESCRIPTION("Samsung Backlight driver");
16582d70b73aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
1659