12d70b73aSGreg Kroah-Hartman /*
22d70b73aSGreg Kroah-Hartman  * Samsung Laptop driver
32d70b73aSGreg Kroah-Hartman  *
42d70b73aSGreg Kroah-Hartman  * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
52d70b73aSGreg Kroah-Hartman  * Copyright (C) 2009,2011 Novell Inc.
62d70b73aSGreg Kroah-Hartman  *
72d70b73aSGreg Kroah-Hartman  * This program is free software; you can redistribute it and/or modify it
82d70b73aSGreg Kroah-Hartman  * under the terms of the GNU General Public License version 2 as published by
92d70b73aSGreg Kroah-Hartman  * the Free Software Foundation.
102d70b73aSGreg Kroah-Hartman  *
112d70b73aSGreg Kroah-Hartman  */
122d70b73aSGreg Kroah-Hartman #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
132d70b73aSGreg Kroah-Hartman 
142d70b73aSGreg Kroah-Hartman #include <linux/kernel.h>
152d70b73aSGreg Kroah-Hartman #include <linux/init.h>
162d70b73aSGreg Kroah-Hartman #include <linux/module.h>
172d70b73aSGreg Kroah-Hartman #include <linux/delay.h>
182d70b73aSGreg Kroah-Hartman #include <linux/pci.h>
192d70b73aSGreg Kroah-Hartman #include <linux/backlight.h>
20f674ebf1SCorentin Chary #include <linux/leds.h>
212d70b73aSGreg Kroah-Hartman #include <linux/fb.h>
222d70b73aSGreg Kroah-Hartman #include <linux/dmi.h>
232d70b73aSGreg Kroah-Hartman #include <linux/platform_device.h>
242d70b73aSGreg Kroah-Hartman #include <linux/rfkill.h>
25f34cd9caSCorentin Chary #include <linux/acpi.h>
265b80fc40SCorentin Chary #include <linux/seq_file.h>
275b80fc40SCorentin Chary #include <linux/debugfs.h>
2882c333aaSDavid Rientjes #include <linux/ctype.h>
29e0094244SMatt Fleming #include <linux/efi.h>
300ca849eaSScott Thrasher #include <linux/suspend.h>
31a979e2e2SCorentin Chary #include <acpi/video.h>
322d70b73aSGreg Kroah-Hartman 
332d70b73aSGreg Kroah-Hartman /*
342d70b73aSGreg Kroah-Hartman  * This driver is needed because a number of Samsung laptops do not hook
352d70b73aSGreg Kroah-Hartman  * their control settings through ACPI.  So we have to poke around in the
362d70b73aSGreg Kroah-Hartman  * BIOS to do things like brightness values, and "special" key controls.
372d70b73aSGreg Kroah-Hartman  */
382d70b73aSGreg Kroah-Hartman 
392d70b73aSGreg Kroah-Hartman /*
402d70b73aSGreg Kroah-Hartman  * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
412d70b73aSGreg Kroah-Hartman  * be reserved by the BIOS (which really doesn't make much sense), we tell
422d70b73aSGreg Kroah-Hartman  * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
432d70b73aSGreg Kroah-Hartman  */
442d70b73aSGreg Kroah-Hartman #define MAX_BRIGHT	0x07
452d70b73aSGreg Kroah-Hartman 
462d70b73aSGreg Kroah-Hartman 
472d70b73aSGreg Kroah-Hartman #define SABI_IFACE_MAIN			0x00
482d70b73aSGreg Kroah-Hartman #define SABI_IFACE_SUB			0x02
492d70b73aSGreg Kroah-Hartman #define SABI_IFACE_COMPLETE		0x04
502d70b73aSGreg Kroah-Hartman #define SABI_IFACE_DATA			0x05
512d70b73aSGreg Kroah-Hartman 
5284d482f2SCorentin Chary #define WL_STATUS_WLAN			0x0
5384d482f2SCorentin Chary #define WL_STATUS_BT			0x2
5484d482f2SCorentin Chary 
557e960711SCorentin Chary /* Structure get/set data using sabi */
567e960711SCorentin Chary struct sabi_data {
577e960711SCorentin Chary 	union {
587e960711SCorentin Chary 		struct {
597e960711SCorentin Chary 			u32 d0;
607e960711SCorentin Chary 			u32 d1;
617e960711SCorentin Chary 			u16 d2;
627e960711SCorentin Chary 			u8  d3;
637e960711SCorentin Chary 		};
647e960711SCorentin Chary 		u8 data[11];
657e960711SCorentin Chary 	};
662d70b73aSGreg Kroah-Hartman };
672d70b73aSGreg Kroah-Hartman 
682d70b73aSGreg Kroah-Hartman struct sabi_header_offsets {
692d70b73aSGreg Kroah-Hartman 	u8 port;
702d70b73aSGreg Kroah-Hartman 	u8 re_mem;
712d70b73aSGreg Kroah-Hartman 	u8 iface_func;
722d70b73aSGreg Kroah-Hartman 	u8 en_mem;
732d70b73aSGreg Kroah-Hartman 	u8 data_offset;
742d70b73aSGreg Kroah-Hartman 	u8 data_segment;
752d70b73aSGreg Kroah-Hartman };
762d70b73aSGreg Kroah-Hartman 
772d70b73aSGreg Kroah-Hartman struct sabi_commands {
782d70b73aSGreg Kroah-Hartman 	/*
792d70b73aSGreg Kroah-Hartman 	 * Brightness is 0 - 8, as described above.
802d70b73aSGreg Kroah-Hartman 	 * Value 0 is for the BIOS to use
812d70b73aSGreg Kroah-Hartman 	 */
827e960711SCorentin Chary 	u16 get_brightness;
837e960711SCorentin Chary 	u16 set_brightness;
842d70b73aSGreg Kroah-Hartman 
852d70b73aSGreg Kroah-Hartman 	/*
862d70b73aSGreg Kroah-Hartman 	 * first byte:
872d70b73aSGreg Kroah-Hartman 	 * 0x00 - wireless is off
882d70b73aSGreg Kroah-Hartman 	 * 0x01 - wireless is on
892d70b73aSGreg Kroah-Hartman 	 * second byte:
902d70b73aSGreg Kroah-Hartman 	 * 0x02 - 3G is off
912d70b73aSGreg Kroah-Hartman 	 * 0x03 - 3G is on
922d70b73aSGreg Kroah-Hartman 	 * TODO, verify 3G is correct, that doesn't seem right...
932d70b73aSGreg Kroah-Hartman 	 */
947e960711SCorentin Chary 	u16 get_wireless_button;
957e960711SCorentin Chary 	u16 set_wireless_button;
962d70b73aSGreg Kroah-Hartman 
972d70b73aSGreg Kroah-Hartman 	/* 0 is off, 1 is on */
987e960711SCorentin Chary 	u16 get_backlight;
997e960711SCorentin Chary 	u16 set_backlight;
1002d70b73aSGreg Kroah-Hartman 
1012d70b73aSGreg Kroah-Hartman 	/*
1022d70b73aSGreg Kroah-Hartman 	 * 0x80 or 0x00 - no action
1032d70b73aSGreg Kroah-Hartman 	 * 0x81 - recovery key pressed
1042d70b73aSGreg Kroah-Hartman 	 */
1057e960711SCorentin Chary 	u16 get_recovery_mode;
1067e960711SCorentin Chary 	u16 set_recovery_mode;
1072d70b73aSGreg Kroah-Hartman 
1082d70b73aSGreg Kroah-Hartman 	/*
1092d70b73aSGreg Kroah-Hartman 	 * on seclinux: 0 is low, 1 is high,
1102d70b73aSGreg Kroah-Hartman 	 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
1112d70b73aSGreg Kroah-Hartman 	 */
1127e960711SCorentin Chary 	u16 get_performance_level;
1137e960711SCorentin Chary 	u16 set_performance_level;
1142d70b73aSGreg Kroah-Hartman 
115cb5b5c91SCorentin Chary 	/* 0x80 is off, 0x81 is on */
116cb5b5c91SCorentin Chary 	u16 get_battery_life_extender;
117cb5b5c91SCorentin Chary 	u16 set_battery_life_extender;
118cb5b5c91SCorentin Chary 
1193a75d378SCorentin Chary 	/* 0x80 is off, 0x81 is on */
1203a75d378SCorentin Chary 	u16 get_usb_charge;
1213a75d378SCorentin Chary 	u16 set_usb_charge;
1223a75d378SCorentin Chary 
12384d482f2SCorentin Chary 	/* the first byte is for bluetooth and the third one is for wlan */
12484d482f2SCorentin Chary 	u16 get_wireless_status;
12584d482f2SCorentin Chary 	u16 set_wireless_status;
12684d482f2SCorentin Chary 
127b0dcaf4fSJulijonas Kikutis 	/* 0x80 is off, 0x81 is on */
128b0dcaf4fSJulijonas Kikutis 	u16 get_lid_handling;
129b0dcaf4fSJulijonas Kikutis 	u16 set_lid_handling;
130b0dcaf4fSJulijonas Kikutis 
131f674ebf1SCorentin Chary 	/* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
132f674ebf1SCorentin Chary 	u16 kbd_backlight;
133f674ebf1SCorentin Chary 
1342d70b73aSGreg Kroah-Hartman 	/*
1352d70b73aSGreg Kroah-Hartman 	 * Tell the BIOS that Linux is running on this machine.
1362d70b73aSGreg Kroah-Hartman 	 * 81 is on, 80 is off
1372d70b73aSGreg Kroah-Hartman 	 */
1387e960711SCorentin Chary 	u16 set_linux;
1392d70b73aSGreg Kroah-Hartman };
1402d70b73aSGreg Kroah-Hartman 
1412d70b73aSGreg Kroah-Hartman struct sabi_performance_level {
1422d70b73aSGreg Kroah-Hartman 	const char *name;
1437e960711SCorentin Chary 	u16 value;
1442d70b73aSGreg Kroah-Hartman };
1452d70b73aSGreg Kroah-Hartman 
1462d70b73aSGreg Kroah-Hartman struct sabi_config {
14784d482f2SCorentin Chary 	int sabi_version;
1482d70b73aSGreg Kroah-Hartman 	const char *test_string;
1492d70b73aSGreg Kroah-Hartman 	u16 main_function;
1502d70b73aSGreg Kroah-Hartman 	const struct sabi_header_offsets header_offsets;
1512d70b73aSGreg Kroah-Hartman 	const struct sabi_commands commands;
1522d70b73aSGreg Kroah-Hartman 	const struct sabi_performance_level performance_levels[4];
1532d70b73aSGreg Kroah-Hartman 	u8 min_brightness;
1542d70b73aSGreg Kroah-Hartman 	u8 max_brightness;
1552d70b73aSGreg Kroah-Hartman };
1562d70b73aSGreg Kroah-Hartman 
1572d70b73aSGreg Kroah-Hartman static const struct sabi_config sabi_configs[] = {
1582d70b73aSGreg Kroah-Hartman 	{
15984d482f2SCorentin Chary 		/* I don't know if it is really 2, but it it is
16084d482f2SCorentin Chary 		 * less than 3 anyway */
16184d482f2SCorentin Chary 		.sabi_version = 2,
16284d482f2SCorentin Chary 
1632d70b73aSGreg Kroah-Hartman 		.test_string = "SECLINUX",
1642d70b73aSGreg Kroah-Hartman 
1652d70b73aSGreg Kroah-Hartman 		.main_function = 0x4c49,
1662d70b73aSGreg Kroah-Hartman 
1672d70b73aSGreg Kroah-Hartman 		.header_offsets = {
1682d70b73aSGreg Kroah-Hartman 			.port = 0x00,
1692d70b73aSGreg Kroah-Hartman 			.re_mem = 0x02,
1702d70b73aSGreg Kroah-Hartman 			.iface_func = 0x03,
1712d70b73aSGreg Kroah-Hartman 			.en_mem = 0x04,
1722d70b73aSGreg Kroah-Hartman 			.data_offset = 0x05,
1732d70b73aSGreg Kroah-Hartman 			.data_segment = 0x07,
1742d70b73aSGreg Kroah-Hartman 		},
1752d70b73aSGreg Kroah-Hartman 
1762d70b73aSGreg Kroah-Hartman 		.commands = {
1772d70b73aSGreg Kroah-Hartman 			.get_brightness = 0x00,
1782d70b73aSGreg Kroah-Hartman 			.set_brightness = 0x01,
1792d70b73aSGreg Kroah-Hartman 
1802d70b73aSGreg Kroah-Hartman 			.get_wireless_button = 0x02,
1812d70b73aSGreg Kroah-Hartman 			.set_wireless_button = 0x03,
1822d70b73aSGreg Kroah-Hartman 
1832d70b73aSGreg Kroah-Hartman 			.get_backlight = 0x04,
1842d70b73aSGreg Kroah-Hartman 			.set_backlight = 0x05,
1852d70b73aSGreg Kroah-Hartman 
1862d70b73aSGreg Kroah-Hartman 			.get_recovery_mode = 0x06,
1872d70b73aSGreg Kroah-Hartman 			.set_recovery_mode = 0x07,
1882d70b73aSGreg Kroah-Hartman 
1892d70b73aSGreg Kroah-Hartman 			.get_performance_level = 0x08,
1902d70b73aSGreg Kroah-Hartman 			.set_performance_level = 0x09,
1912d70b73aSGreg Kroah-Hartman 
192cb5b5c91SCorentin Chary 			.get_battery_life_extender = 0xFFFF,
193cb5b5c91SCorentin Chary 			.set_battery_life_extender = 0xFFFF,
194cb5b5c91SCorentin Chary 
1953a75d378SCorentin Chary 			.get_usb_charge = 0xFFFF,
1963a75d378SCorentin Chary 			.set_usb_charge = 0xFFFF,
1973a75d378SCorentin Chary 
19884d482f2SCorentin Chary 			.get_wireless_status = 0xFFFF,
19984d482f2SCorentin Chary 			.set_wireless_status = 0xFFFF,
20084d482f2SCorentin Chary 
201b0dcaf4fSJulijonas Kikutis 			.get_lid_handling = 0xFFFF,
202b0dcaf4fSJulijonas Kikutis 			.set_lid_handling = 0xFFFF,
203b0dcaf4fSJulijonas Kikutis 
204f674ebf1SCorentin Chary 			.kbd_backlight = 0xFFFF,
205f674ebf1SCorentin Chary 
2062d70b73aSGreg Kroah-Hartman 			.set_linux = 0x0a,
2072d70b73aSGreg Kroah-Hartman 		},
2082d70b73aSGreg Kroah-Hartman 
2092d70b73aSGreg Kroah-Hartman 		.performance_levels = {
2102d70b73aSGreg Kroah-Hartman 			{
2112d70b73aSGreg Kroah-Hartman 				.name = "silent",
2122d70b73aSGreg Kroah-Hartman 				.value = 0,
2132d70b73aSGreg Kroah-Hartman 			},
2142d70b73aSGreg Kroah-Hartman 			{
2152d70b73aSGreg Kroah-Hartman 				.name = "normal",
2162d70b73aSGreg Kroah-Hartman 				.value = 1,
2172d70b73aSGreg Kroah-Hartman 			},
2182d70b73aSGreg Kroah-Hartman 			{ },
2192d70b73aSGreg Kroah-Hartman 		},
2202d70b73aSGreg Kroah-Hartman 		.min_brightness = 1,
2212d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
2222d70b73aSGreg Kroah-Hartman 	},
2232d70b73aSGreg Kroah-Hartman 	{
22484d482f2SCorentin Chary 		.sabi_version = 3,
22584d482f2SCorentin Chary 
2262d70b73aSGreg Kroah-Hartman 		.test_string = "SwSmi@",
2272d70b73aSGreg Kroah-Hartman 
2282d70b73aSGreg Kroah-Hartman 		.main_function = 0x5843,
2292d70b73aSGreg Kroah-Hartman 
2302d70b73aSGreg Kroah-Hartman 		.header_offsets = {
2312d70b73aSGreg Kroah-Hartman 			.port = 0x00,
2322d70b73aSGreg Kroah-Hartman 			.re_mem = 0x04,
2332d70b73aSGreg Kroah-Hartman 			.iface_func = 0x02,
2342d70b73aSGreg Kroah-Hartman 			.en_mem = 0x03,
2352d70b73aSGreg Kroah-Hartman 			.data_offset = 0x05,
2362d70b73aSGreg Kroah-Hartman 			.data_segment = 0x07,
2372d70b73aSGreg Kroah-Hartman 		},
2382d70b73aSGreg Kroah-Hartman 
2392d70b73aSGreg Kroah-Hartman 		.commands = {
2402d70b73aSGreg Kroah-Hartman 			.get_brightness = 0x10,
2412d70b73aSGreg Kroah-Hartman 			.set_brightness = 0x11,
2422d70b73aSGreg Kroah-Hartman 
2432d70b73aSGreg Kroah-Hartman 			.get_wireless_button = 0x12,
2442d70b73aSGreg Kroah-Hartman 			.set_wireless_button = 0x13,
2452d70b73aSGreg Kroah-Hartman 
2462d70b73aSGreg Kroah-Hartman 			.get_backlight = 0x2d,
2472d70b73aSGreg Kroah-Hartman 			.set_backlight = 0x2e,
2482d70b73aSGreg Kroah-Hartman 
2492d70b73aSGreg Kroah-Hartman 			.get_recovery_mode = 0xff,
2502d70b73aSGreg Kroah-Hartman 			.set_recovery_mode = 0xff,
2512d70b73aSGreg Kroah-Hartman 
2522d70b73aSGreg Kroah-Hartman 			.get_performance_level = 0x31,
2532d70b73aSGreg Kroah-Hartman 			.set_performance_level = 0x32,
2542d70b73aSGreg Kroah-Hartman 
255cb5b5c91SCorentin Chary 			.get_battery_life_extender = 0x65,
256cb5b5c91SCorentin Chary 			.set_battery_life_extender = 0x66,
257cb5b5c91SCorentin Chary 
2583a75d378SCorentin Chary 			.get_usb_charge = 0x67,
2593a75d378SCorentin Chary 			.set_usb_charge = 0x68,
2603a75d378SCorentin Chary 
26184d482f2SCorentin Chary 			.get_wireless_status = 0x69,
26284d482f2SCorentin Chary 			.set_wireless_status = 0x6a,
26384d482f2SCorentin Chary 
264b0dcaf4fSJulijonas Kikutis 			.get_lid_handling = 0x6d,
265b0dcaf4fSJulijonas Kikutis 			.set_lid_handling = 0x6e,
266b0dcaf4fSJulijonas Kikutis 
267f674ebf1SCorentin Chary 			.kbd_backlight = 0x78,
268f674ebf1SCorentin Chary 
2692d70b73aSGreg Kroah-Hartman 			.set_linux = 0xff,
2702d70b73aSGreg Kroah-Hartman 		},
2712d70b73aSGreg Kroah-Hartman 
2722d70b73aSGreg Kroah-Hartman 		.performance_levels = {
2732d70b73aSGreg Kroah-Hartman 			{
2742d70b73aSGreg Kroah-Hartman 				.name = "normal",
2752d70b73aSGreg Kroah-Hartman 				.value = 0,
2762d70b73aSGreg Kroah-Hartman 			},
2772d70b73aSGreg Kroah-Hartman 			{
2782d70b73aSGreg Kroah-Hartman 				.name = "silent",
2792d70b73aSGreg Kroah-Hartman 				.value = 1,
2802d70b73aSGreg Kroah-Hartman 			},
2812d70b73aSGreg Kroah-Hartman 			{
2822d70b73aSGreg Kroah-Hartman 				.name = "overclock",
2832d70b73aSGreg Kroah-Hartman 				.value = 2,
2842d70b73aSGreg Kroah-Hartman 			},
2852d70b73aSGreg Kroah-Hartman 			{ },
2862d70b73aSGreg Kroah-Hartman 		},
2872d70b73aSGreg Kroah-Hartman 		.min_brightness = 0,
2882d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
2892d70b73aSGreg Kroah-Hartman 	},
2902d70b73aSGreg Kroah-Hartman 	{ },
2912d70b73aSGreg Kroah-Hartman };
2922d70b73aSGreg Kroah-Hartman 
2935b80fc40SCorentin Chary /*
2945b80fc40SCorentin Chary  * samsung-laptop/    - debugfs root directory
2955b80fc40SCorentin Chary  *   f0000_segment    - dump f0000 segment
2965b80fc40SCorentin Chary  *   command          - current command
2975b80fc40SCorentin Chary  *   data             - current data
2985b80fc40SCorentin Chary  *   d0, d1, d2, d3   - data fields
2995b80fc40SCorentin Chary  *   call             - call SABI using command and data
3005b80fc40SCorentin Chary  *
3015b80fc40SCorentin Chary  * This allow to call arbitrary sabi commands wihout
3025b80fc40SCorentin Chary  * modifying the driver at all.
3035b80fc40SCorentin Chary  * For example, setting the keyboard backlight brightness to 5
3045b80fc40SCorentin Chary  *
3055b80fc40SCorentin Chary  *  echo 0x78 > command
3065b80fc40SCorentin Chary  *  echo 0x0582 > d0
3075b80fc40SCorentin Chary  *  echo 0 > d1
3085b80fc40SCorentin Chary  *  echo 0 > d2
3095b80fc40SCorentin Chary  *  echo 0 > d3
3105b80fc40SCorentin Chary  *  cat call
3115b80fc40SCorentin Chary  */
3125b80fc40SCorentin Chary 
3135b80fc40SCorentin Chary struct samsung_laptop_debug {
3145b80fc40SCorentin Chary 	struct dentry *root;
3155b80fc40SCorentin Chary 	struct sabi_data data;
3165b80fc40SCorentin Chary 	u16 command;
3175b80fc40SCorentin Chary 
3185b80fc40SCorentin Chary 	struct debugfs_blob_wrapper f0000_wrapper;
3195b80fc40SCorentin Chary 	struct debugfs_blob_wrapper data_wrapper;
3206f6ae06eSCorentin Chary 	struct debugfs_blob_wrapper sdiag_wrapper;
3215b80fc40SCorentin Chary };
3225b80fc40SCorentin Chary 
32384d482f2SCorentin Chary struct samsung_laptop;
32484d482f2SCorentin Chary 
32584d482f2SCorentin Chary struct samsung_rfkill {
32684d482f2SCorentin Chary 	struct samsung_laptop *samsung;
32784d482f2SCorentin Chary 	struct rfkill *rfkill;
32884d482f2SCorentin Chary 	enum rfkill_type type;
32984d482f2SCorentin Chary };
33084d482f2SCorentin Chary 
331a6df4894SCorentin Chary struct samsung_laptop {
332a6df4894SCorentin Chary 	const struct sabi_config *config;
3332d70b73aSGreg Kroah-Hartman 
334a6df4894SCorentin Chary 	void __iomem *sabi;
335a6df4894SCorentin Chary 	void __iomem *sabi_iface;
336a6df4894SCorentin Chary 	void __iomem *f0000_segment;
337a6df4894SCorentin Chary 
338a6df4894SCorentin Chary 	struct mutex sabi_mutex;
339a6df4894SCorentin Chary 
3405dea7a20SCorentin Chary 	struct platform_device *platform_device;
341a6df4894SCorentin Chary 	struct backlight_device *backlight_device;
34284d482f2SCorentin Chary 
34384d482f2SCorentin Chary 	struct samsung_rfkill wlan;
34484d482f2SCorentin Chary 	struct samsung_rfkill bluetooth;
345a6df4894SCorentin Chary 
346f674ebf1SCorentin Chary 	struct led_classdev kbd_led;
347f674ebf1SCorentin Chary 	int kbd_led_wk;
348f674ebf1SCorentin Chary 	struct workqueue_struct *led_workqueue;
349f674ebf1SCorentin Chary 	struct work_struct kbd_led_work;
350f674ebf1SCorentin Chary 
3515b80fc40SCorentin Chary 	struct samsung_laptop_debug debug;
352a979e2e2SCorentin Chary 	struct samsung_quirks *quirks;
3535b80fc40SCorentin Chary 
3540ca849eaSScott Thrasher 	struct notifier_block pm_nb;
3550ca849eaSScott Thrasher 
356f34cd9caSCorentin Chary 	bool handle_backlight;
357a6df4894SCorentin Chary 	bool has_stepping_quirk;
3586f6ae06eSCorentin Chary 
3596f6ae06eSCorentin Chary 	char sdiag[64];
360a6df4894SCorentin Chary };
361a6df4894SCorentin Chary 
362a979e2e2SCorentin Chary struct samsung_quirks {
363a979e2e2SCorentin Chary 	bool broken_acpi_video;
3640ca849eaSScott Thrasher 	bool four_kbd_backlight_levels;
3650ca849eaSScott Thrasher 	bool enable_kbd_backlight;
3664690555eSHans de Goede 	bool use_native_backlight;
367b0dcaf4fSJulijonas Kikutis 	bool lid_handling;
368a979e2e2SCorentin Chary };
3695dea7a20SCorentin Chary 
370a979e2e2SCorentin Chary static struct samsung_quirks samsung_unknown = {};
371a979e2e2SCorentin Chary 
372a979e2e2SCorentin Chary static struct samsung_quirks samsung_broken_acpi_video = {
373a979e2e2SCorentin Chary 	.broken_acpi_video = true,
374a979e2e2SCorentin Chary };
3752d70b73aSGreg Kroah-Hartman 
3764690555eSHans de Goede static struct samsung_quirks samsung_use_native_backlight = {
3774690555eSHans de Goede 	.use_native_backlight = true,
3784690555eSHans de Goede };
3794690555eSHans de Goede 
3800ca849eaSScott Thrasher static struct samsung_quirks samsung_np740u3e = {
3810ca849eaSScott Thrasher 	.four_kbd_backlight_levels = true,
3820ca849eaSScott Thrasher 	.enable_kbd_backlight = true,
3830ca849eaSScott Thrasher };
3840ca849eaSScott Thrasher 
385b0dcaf4fSJulijonas Kikutis static struct samsung_quirks samsung_lid_handling = {
386b0dcaf4fSJulijonas Kikutis 	.lid_handling = true,
387b0dcaf4fSJulijonas Kikutis };
388b0dcaf4fSJulijonas Kikutis 
38990ab5ee9SRusty Russell static bool force;
3902d70b73aSGreg Kroah-Hartman module_param(force, bool, 0);
3912d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(force,
3922d70b73aSGreg Kroah-Hartman 		"Disable the DMI check and forces the driver to be loaded");
3932d70b73aSGreg Kroah-Hartman 
39490ab5ee9SRusty Russell static bool debug;
3952d70b73aSGreg Kroah-Hartman module_param(debug, bool, S_IRUGO | S_IWUSR);
3962d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(debug, "Debug enabled or not");
3972d70b73aSGreg Kroah-Hartman 
3987e960711SCorentin Chary static int sabi_command(struct samsung_laptop *samsung, u16 command,
3997e960711SCorentin Chary 			struct sabi_data *in,
4007e960711SCorentin Chary 			struct sabi_data *out)
4012d70b73aSGreg Kroah-Hartman {
402a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
4037e960711SCorentin Chary 	int ret = 0;
404a6df4894SCorentin Chary 	u16 port = readw(samsung->sabi + config->header_offsets.port);
4052d70b73aSGreg Kroah-Hartman 	u8 complete, iface_data;
4062d70b73aSGreg Kroah-Hartman 
407a6df4894SCorentin Chary 	mutex_lock(&samsung->sabi_mutex);
4082d70b73aSGreg Kroah-Hartman 
4097e960711SCorentin Chary 	if (debug) {
4107e960711SCorentin Chary 		if (in)
4112e777187SCorentin Chary 			pr_info("SABI command:0x%04x "
4122e777187SCorentin Chary 				"data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
4137e960711SCorentin Chary 				command, in->d0, in->d1, in->d2, in->d3);
4147e960711SCorentin Chary 		else
4152e777187SCorentin Chary 			pr_info("SABI command:0x%04x", command);
4167e960711SCorentin Chary 	}
4177e960711SCorentin Chary 
4182d70b73aSGreg Kroah-Hartman 	/* enable memory to be able to write to it */
419a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
4202d70b73aSGreg Kroah-Hartman 
4212d70b73aSGreg Kroah-Hartman 	/* write out the command */
422a6df4894SCorentin Chary 	writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
423a6df4894SCorentin Chary 	writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
424a6df4894SCorentin Chary 	writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
4257e960711SCorentin Chary 	if (in) {
4267e960711SCorentin Chary 		writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
4277e960711SCorentin Chary 		writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
4287e960711SCorentin Chary 		writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
4297e960711SCorentin Chary 		writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
4307e960711SCorentin Chary 	}
431a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
4322d70b73aSGreg Kroah-Hartman 
4332d70b73aSGreg Kroah-Hartman 	/* write protect memory to make it safe */
434a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
4352d70b73aSGreg Kroah-Hartman 
4362d70b73aSGreg Kroah-Hartman 	/* see if the command actually succeeded */
437a6df4894SCorentin Chary 	complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
438a6df4894SCorentin Chary 	iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
4392e777187SCorentin Chary 
4402e777187SCorentin Chary 	/* iface_data = 0xFF happens when a command is not known
4412e777187SCorentin Chary 	 * so we only add a warning in debug mode since we will
4422e777187SCorentin Chary 	 * probably issue some unknown command at startup to find
4432e777187SCorentin Chary 	 * out which features are supported */
4442e777187SCorentin Chary 	if (complete != 0xaa || (iface_data == 0xff && debug))
4457e960711SCorentin Chary 		pr_warn("SABI command 0x%04x failed with"
4467e960711SCorentin Chary 			" completion flag 0x%02x and interface data 0x%02x",
4472d70b73aSGreg Kroah-Hartman 			command, complete, iface_data);
4482e777187SCorentin Chary 
4492e777187SCorentin Chary 	if (complete != 0xaa || iface_data == 0xff) {
4507e960711SCorentin Chary 		ret = -EINVAL;
4512d70b73aSGreg Kroah-Hartman 		goto exit;
4522d70b73aSGreg Kroah-Hartman 	}
4537e960711SCorentin Chary 
4547e960711SCorentin Chary 	if (out) {
4557e960711SCorentin Chary 		out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
4567e960711SCorentin Chary 		out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
4577e960711SCorentin Chary 		out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
4587e960711SCorentin Chary 		out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
4597e960711SCorentin Chary 	}
4607e960711SCorentin Chary 
4617e960711SCorentin Chary 	if (debug && out) {
4622e777187SCorentin Chary 		pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
4637e960711SCorentin Chary 			out->d0, out->d1, out->d2, out->d3);
4647e960711SCorentin Chary 	}
4652d70b73aSGreg Kroah-Hartman 
4662d70b73aSGreg Kroah-Hartman exit:
467a6df4894SCorentin Chary 	mutex_unlock(&samsung->sabi_mutex);
4687e960711SCorentin Chary 	return ret;
4692d70b73aSGreg Kroah-Hartman }
4702d70b73aSGreg Kroah-Hartman 
4717e960711SCorentin Chary /* simple wrappers usable with most commands */
4727e960711SCorentin Chary static int sabi_set_commandb(struct samsung_laptop *samsung,
4737e960711SCorentin Chary 			     u16 command, u8 data)
4742d70b73aSGreg Kroah-Hartman {
47585229440SDavid Rientjes 	struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
4762d70b73aSGreg Kroah-Hartman 
4777e960711SCorentin Chary 	in.data[0] = data;
4787e960711SCorentin Chary 	return sabi_command(samsung, command, &in, NULL);
4792d70b73aSGreg Kroah-Hartman }
4802d70b73aSGreg Kroah-Hartman 
4815dea7a20SCorentin Chary static int read_brightness(struct samsung_laptop *samsung)
4822d70b73aSGreg Kroah-Hartman {
483a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
484a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
4857e960711SCorentin Chary 	struct sabi_data sretval;
4862d70b73aSGreg Kroah-Hartman 	int user_brightness = 0;
4872d70b73aSGreg Kroah-Hartman 	int retval;
4882d70b73aSGreg Kroah-Hartman 
4897e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_brightness,
4907e960711SCorentin Chary 			      NULL, &sretval);
4917e960711SCorentin Chary 	if (retval)
4927e960711SCorentin Chary 		return retval;
4937e960711SCorentin Chary 
4947e960711SCorentin Chary 	user_brightness = sretval.data[0];
495a6df4894SCorentin Chary 	if (user_brightness > config->min_brightness)
496a6df4894SCorentin Chary 		user_brightness -= config->min_brightness;
497bee460beSJason Stubbs 	else
498bee460beSJason Stubbs 		user_brightness = 0;
4997e960711SCorentin Chary 
5002d70b73aSGreg Kroah-Hartman 	return user_brightness;
5012d70b73aSGreg Kroah-Hartman }
5022d70b73aSGreg Kroah-Hartman 
5035dea7a20SCorentin Chary static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
5042d70b73aSGreg Kroah-Hartman {
505a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
506a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
507a6df4894SCorentin Chary 	u8 user_level = user_brightness + config->min_brightness;
5082d70b73aSGreg Kroah-Hartman 
509a6df4894SCorentin Chary 	if (samsung->has_stepping_quirk && user_level != 0) {
510ac080523SJason Stubbs 		/*
511ac080523SJason Stubbs 		 * short circuit if the specified level is what's already set
512ac080523SJason Stubbs 		 * to prevent the screen from flickering needlessly
513ac080523SJason Stubbs 		 */
5145dea7a20SCorentin Chary 		if (user_brightness == read_brightness(samsung))
515ac080523SJason Stubbs 			return;
516ac080523SJason Stubbs 
5177e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_brightness, 0);
518ac080523SJason Stubbs 	}
519ac080523SJason Stubbs 
5207e960711SCorentin Chary 	sabi_set_commandb(samsung, commands->set_brightness, user_level);
5212d70b73aSGreg Kroah-Hartman }
5222d70b73aSGreg Kroah-Hartman 
5232d70b73aSGreg Kroah-Hartman static int get_brightness(struct backlight_device *bd)
5242d70b73aSGreg Kroah-Hartman {
5255dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
5265dea7a20SCorentin Chary 
5275dea7a20SCorentin Chary 	return read_brightness(samsung);
5282d70b73aSGreg Kroah-Hartman }
5292d70b73aSGreg Kroah-Hartman 
5305dea7a20SCorentin Chary static void check_for_stepping_quirk(struct samsung_laptop *samsung)
531ac080523SJason Stubbs {
5325dea7a20SCorentin Chary 	int initial_level;
5335dea7a20SCorentin Chary 	int check_level;
5345dea7a20SCorentin Chary 	int orig_level = read_brightness(samsung);
535ac080523SJason Stubbs 
536ac080523SJason Stubbs 	/*
537ac080523SJason Stubbs 	 * Some laptops exhibit the strange behaviour of stepping toward
538ac080523SJason Stubbs 	 * (rather than setting) the brightness except when changing to/from
539ac080523SJason Stubbs 	 * brightness level 0. This behaviour is checked for here and worked
540ac080523SJason Stubbs 	 * around in set_brightness.
541ac080523SJason Stubbs 	 */
542ac080523SJason Stubbs 
543ba05b237SJohn Serock 	if (orig_level == 0)
5445dea7a20SCorentin Chary 		set_brightness(samsung, 1);
545ba05b237SJohn Serock 
5465dea7a20SCorentin Chary 	initial_level = read_brightness(samsung);
547ba05b237SJohn Serock 
548ac080523SJason Stubbs 	if (initial_level <= 2)
549ac080523SJason Stubbs 		check_level = initial_level + 2;
550ac080523SJason Stubbs 	else
551ac080523SJason Stubbs 		check_level = initial_level - 2;
552ac080523SJason Stubbs 
553a6df4894SCorentin Chary 	samsung->has_stepping_quirk = false;
5545dea7a20SCorentin Chary 	set_brightness(samsung, check_level);
555ac080523SJason Stubbs 
5565dea7a20SCorentin Chary 	if (read_brightness(samsung) != check_level) {
557a6df4894SCorentin Chary 		samsung->has_stepping_quirk = true;
558ac080523SJason Stubbs 		pr_info("enabled workaround for brightness stepping quirk\n");
559ac080523SJason Stubbs 	}
560ac080523SJason Stubbs 
5615dea7a20SCorentin Chary 	set_brightness(samsung, orig_level);
562ac080523SJason Stubbs }
563ac080523SJason Stubbs 
5642d70b73aSGreg Kroah-Hartman static int update_status(struct backlight_device *bd)
5652d70b73aSGreg Kroah-Hartman {
5665dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
567a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
568a6df4894SCorentin Chary 
5695dea7a20SCorentin Chary 	set_brightness(samsung, bd->props.brightness);
5702d70b73aSGreg Kroah-Hartman 
5712d70b73aSGreg Kroah-Hartman 	if (bd->props.power == FB_BLANK_UNBLANK)
5727e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 1);
5732d70b73aSGreg Kroah-Hartman 	else
5747e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 0);
5755dea7a20SCorentin Chary 
5762d70b73aSGreg Kroah-Hartman 	return 0;
5772d70b73aSGreg Kroah-Hartman }
5782d70b73aSGreg Kroah-Hartman 
5792d70b73aSGreg Kroah-Hartman static const struct backlight_ops backlight_ops = {
5802d70b73aSGreg Kroah-Hartman 	.get_brightness	= get_brightness,
5812d70b73aSGreg Kroah-Hartman 	.update_status	= update_status,
5822d70b73aSGreg Kroah-Hartman };
5832d70b73aSGreg Kroah-Hartman 
58484d482f2SCorentin Chary static int seclinux_rfkill_set(void *data, bool blocked)
5852d70b73aSGreg Kroah-Hartman {
58620db88e3SCorentin Chary 	struct samsung_rfkill *srfkill = data;
58720db88e3SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
588a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
589a6df4894SCorentin Chary 
59084d482f2SCorentin Chary 	return sabi_set_commandb(samsung, commands->set_wireless_button,
59184d482f2SCorentin Chary 				 !blocked);
5922d70b73aSGreg Kroah-Hartman }
5932d70b73aSGreg Kroah-Hartman 
5948314a1c8SBhumika Goyal static const struct rfkill_ops seclinux_rfkill_ops = {
59584d482f2SCorentin Chary 	.set_block = seclinux_rfkill_set,
59684d482f2SCorentin Chary };
59784d482f2SCorentin Chary 
59884d482f2SCorentin Chary static int swsmi_wireless_status(struct samsung_laptop *samsung,
59984d482f2SCorentin Chary 				 struct sabi_data *data)
60084d482f2SCorentin Chary {
60184d482f2SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
60284d482f2SCorentin Chary 
60384d482f2SCorentin Chary 	return sabi_command(samsung, commands->get_wireless_status,
60484d482f2SCorentin Chary 			    NULL, data);
60584d482f2SCorentin Chary }
60684d482f2SCorentin Chary 
60784d482f2SCorentin Chary static int swsmi_rfkill_set(void *priv, bool blocked)
60884d482f2SCorentin Chary {
60984d482f2SCorentin Chary 	struct samsung_rfkill *srfkill = priv;
61084d482f2SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
61184d482f2SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
61284d482f2SCorentin Chary 	struct sabi_data data;
61384d482f2SCorentin Chary 	int ret, i;
61484d482f2SCorentin Chary 
61584d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
61684d482f2SCorentin Chary 	if (ret)
61784d482f2SCorentin Chary 		return ret;
61884d482f2SCorentin Chary 
61984d482f2SCorentin Chary 	/* Don't set the state for non-present devices */
62084d482f2SCorentin Chary 	for (i = 0; i < 4; i++)
62184d482f2SCorentin Chary 		if (data.data[i] == 0x02)
62284d482f2SCorentin Chary 			data.data[1] = 0;
62384d482f2SCorentin Chary 
62484d482f2SCorentin Chary 	if (srfkill->type == RFKILL_TYPE_WLAN)
62584d482f2SCorentin Chary 		data.data[WL_STATUS_WLAN] = !blocked;
62684d482f2SCorentin Chary 	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
62784d482f2SCorentin Chary 		data.data[WL_STATUS_BT] = !blocked;
62884d482f2SCorentin Chary 
62984d482f2SCorentin Chary 	return sabi_command(samsung, commands->set_wireless_status,
63084d482f2SCorentin Chary 			    &data, &data);
63184d482f2SCorentin Chary }
63284d482f2SCorentin Chary 
63384d482f2SCorentin Chary static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
63484d482f2SCorentin Chary {
63584d482f2SCorentin Chary 	struct samsung_rfkill *srfkill = priv;
63684d482f2SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
63784d482f2SCorentin Chary 	struct sabi_data data;
63884d482f2SCorentin Chary 	int ret;
63984d482f2SCorentin Chary 
64084d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
64184d482f2SCorentin Chary 	if (ret)
64284d482f2SCorentin Chary 		return ;
64384d482f2SCorentin Chary 
64484d482f2SCorentin Chary 	if (srfkill->type == RFKILL_TYPE_WLAN)
64584d482f2SCorentin Chary 		ret = data.data[WL_STATUS_WLAN];
64684d482f2SCorentin Chary 	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
64784d482f2SCorentin Chary 		ret = data.data[WL_STATUS_BT];
64884d482f2SCorentin Chary 	else
64984d482f2SCorentin Chary 		return ;
65084d482f2SCorentin Chary 
65184d482f2SCorentin Chary 	rfkill_set_sw_state(rfkill, !ret);
65284d482f2SCorentin Chary }
65384d482f2SCorentin Chary 
6548314a1c8SBhumika Goyal static const struct rfkill_ops swsmi_rfkill_ops = {
65584d482f2SCorentin Chary 	.set_block = swsmi_rfkill_set,
65684d482f2SCorentin Chary 	.query = swsmi_rfkill_query,
6572d70b73aSGreg Kroah-Hartman };
6582d70b73aSGreg Kroah-Hartman 
6592d70b73aSGreg Kroah-Hartman static ssize_t get_performance_level(struct device *dev,
6602d70b73aSGreg Kroah-Hartman 				     struct device_attribute *attr, char *buf)
6612d70b73aSGreg Kroah-Hartman {
6625dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
663a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
6645dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
6657e960711SCorentin Chary 	struct sabi_data sretval;
6662d70b73aSGreg Kroah-Hartman 	int retval;
6672d70b73aSGreg Kroah-Hartman 	int i;
6682d70b73aSGreg Kroah-Hartman 
6692d70b73aSGreg Kroah-Hartman 	/* Read the state */
6707e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_performance_level,
6717e960711SCorentin Chary 			      NULL, &sretval);
6722d70b73aSGreg Kroah-Hartman 	if (retval)
6732d70b73aSGreg Kroah-Hartman 		return retval;
6742d70b73aSGreg Kroah-Hartman 
6752d70b73aSGreg Kroah-Hartman 	/* The logic is backwards, yeah, lots of fun... */
676a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
6777e960711SCorentin Chary 		if (sretval.data[0] == config->performance_levels[i].value)
678a6df4894SCorentin Chary 			return sprintf(buf, "%s\n", config->performance_levels[i].name);
6792d70b73aSGreg Kroah-Hartman 	}
6802d70b73aSGreg Kroah-Hartman 	return sprintf(buf, "%s\n", "unknown");
6812d70b73aSGreg Kroah-Hartman }
6822d70b73aSGreg Kroah-Hartman 
6832d70b73aSGreg Kroah-Hartman static ssize_t set_performance_level(struct device *dev,
6842d70b73aSGreg Kroah-Hartman 				struct device_attribute *attr, const char *buf,
6852d70b73aSGreg Kroah-Hartman 				size_t count)
6862d70b73aSGreg Kroah-Hartman {
6875dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
688a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
6895dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
6902d70b73aSGreg Kroah-Hartman 	int i;
6915dea7a20SCorentin Chary 
6925dea7a20SCorentin Chary 	if (count < 1)
6935dea7a20SCorentin Chary 		return count;
6945dea7a20SCorentin Chary 
695a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
6962d70b73aSGreg Kroah-Hartman 		const struct sabi_performance_level *level =
697a6df4894SCorentin Chary 			&config->performance_levels[i];
6982d70b73aSGreg Kroah-Hartman 		if (!strncasecmp(level->name, buf, strlen(level->name))) {
6997e960711SCorentin Chary 			sabi_set_commandb(samsung,
7005dea7a20SCorentin Chary 					  commands->set_performance_level,
7012d70b73aSGreg Kroah-Hartman 					  level->value);
7022d70b73aSGreg Kroah-Hartman 			break;
7032d70b73aSGreg Kroah-Hartman 		}
7042d70b73aSGreg Kroah-Hartman 	}
7055dea7a20SCorentin Chary 
706a6df4894SCorentin Chary 	if (!config->performance_levels[i].name)
7072d70b73aSGreg Kroah-Hartman 		return -EINVAL;
7085dea7a20SCorentin Chary 
7092d70b73aSGreg Kroah-Hartman 	return count;
7102d70b73aSGreg Kroah-Hartman }
7115dea7a20SCorentin Chary 
7122d70b73aSGreg Kroah-Hartman static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
7132d70b73aSGreg Kroah-Hartman 		   get_performance_level, set_performance_level);
7142d70b73aSGreg Kroah-Hartman 
715cb5b5c91SCorentin Chary static int read_battery_life_extender(struct samsung_laptop *samsung)
716cb5b5c91SCorentin Chary {
717cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
718cb5b5c91SCorentin Chary 	struct sabi_data data;
719cb5b5c91SCorentin Chary 	int retval;
720cb5b5c91SCorentin Chary 
721cb5b5c91SCorentin Chary 	if (commands->get_battery_life_extender == 0xFFFF)
722cb5b5c91SCorentin Chary 		return -ENODEV;
723cb5b5c91SCorentin Chary 
724cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
725cb5b5c91SCorentin Chary 	data.data[0] = 0x80;
726cb5b5c91SCorentin Chary 	retval = sabi_command(samsung, commands->get_battery_life_extender,
727cb5b5c91SCorentin Chary 			      &data, &data);
728cb5b5c91SCorentin Chary 
729cb5b5c91SCorentin Chary 	if (retval)
730cb5b5c91SCorentin Chary 		return retval;
731cb5b5c91SCorentin Chary 
732cb5b5c91SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
733cb5b5c91SCorentin Chary 		return -ENODEV;
734cb5b5c91SCorentin Chary 
735cb5b5c91SCorentin Chary 	return data.data[0];
736cb5b5c91SCorentin Chary }
737cb5b5c91SCorentin Chary 
738cb5b5c91SCorentin Chary static int write_battery_life_extender(struct samsung_laptop *samsung,
739cb5b5c91SCorentin Chary 				       int enabled)
740cb5b5c91SCorentin Chary {
741cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
742cb5b5c91SCorentin Chary 	struct sabi_data data;
743cb5b5c91SCorentin Chary 
744cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
745cb5b5c91SCorentin Chary 	data.data[0] = 0x80 | enabled;
746cb5b5c91SCorentin Chary 	return sabi_command(samsung, commands->set_battery_life_extender,
747cb5b5c91SCorentin Chary 			    &data, NULL);
748cb5b5c91SCorentin Chary }
749cb5b5c91SCorentin Chary 
750cb5b5c91SCorentin Chary static ssize_t get_battery_life_extender(struct device *dev,
751cb5b5c91SCorentin Chary 					 struct device_attribute *attr,
752cb5b5c91SCorentin Chary 					 char *buf)
753cb5b5c91SCorentin Chary {
754cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
755cb5b5c91SCorentin Chary 	int ret;
756cb5b5c91SCorentin Chary 
757cb5b5c91SCorentin Chary 	ret = read_battery_life_extender(samsung);
758cb5b5c91SCorentin Chary 	if (ret < 0)
759cb5b5c91SCorentin Chary 		return ret;
760cb5b5c91SCorentin Chary 
761cb5b5c91SCorentin Chary 	return sprintf(buf, "%d\n", ret);
762cb5b5c91SCorentin Chary }
763cb5b5c91SCorentin Chary 
764cb5b5c91SCorentin Chary static ssize_t set_battery_life_extender(struct device *dev,
765cb5b5c91SCorentin Chary 					struct device_attribute *attr,
766cb5b5c91SCorentin Chary 					const char *buf, size_t count)
767cb5b5c91SCorentin Chary {
768cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
769cb5b5c91SCorentin Chary 	int ret, value;
770cb5b5c91SCorentin Chary 
771802cf2e1SDarren Hart 	if (!count || kstrtoint(buf, 0, &value) != 0)
772cb5b5c91SCorentin Chary 		return -EINVAL;
773cb5b5c91SCorentin Chary 
774cb5b5c91SCorentin Chary 	ret = write_battery_life_extender(samsung, !!value);
775cb5b5c91SCorentin Chary 	if (ret < 0)
776cb5b5c91SCorentin Chary 		return ret;
777cb5b5c91SCorentin Chary 
778cb5b5c91SCorentin Chary 	return count;
779cb5b5c91SCorentin Chary }
780cb5b5c91SCorentin Chary 
781cb5b5c91SCorentin Chary static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
782cb5b5c91SCorentin Chary 		   get_battery_life_extender, set_battery_life_extender);
783cb5b5c91SCorentin Chary 
7843a75d378SCorentin Chary static int read_usb_charge(struct samsung_laptop *samsung)
7853a75d378SCorentin Chary {
7863a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
7873a75d378SCorentin Chary 	struct sabi_data data;
7883a75d378SCorentin Chary 	int retval;
7893a75d378SCorentin Chary 
7903a75d378SCorentin Chary 	if (commands->get_usb_charge == 0xFFFF)
7913a75d378SCorentin Chary 		return -ENODEV;
7923a75d378SCorentin Chary 
7933a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
7943a75d378SCorentin Chary 	data.data[0] = 0x80;
7953a75d378SCorentin Chary 	retval = sabi_command(samsung, commands->get_usb_charge,
7963a75d378SCorentin Chary 			      &data, &data);
7973a75d378SCorentin Chary 
7983a75d378SCorentin Chary 	if (retval)
7993a75d378SCorentin Chary 		return retval;
8003a75d378SCorentin Chary 
8013a75d378SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
8023a75d378SCorentin Chary 		return -ENODEV;
8033a75d378SCorentin Chary 
8043a75d378SCorentin Chary 	return data.data[0];
8053a75d378SCorentin Chary }
8063a75d378SCorentin Chary 
8073a75d378SCorentin Chary static int write_usb_charge(struct samsung_laptop *samsung,
8083a75d378SCorentin Chary 			    int enabled)
8093a75d378SCorentin Chary {
8103a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
8113a75d378SCorentin Chary 	struct sabi_data data;
8123a75d378SCorentin Chary 
8133a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
8143a75d378SCorentin Chary 	data.data[0] = 0x80 | enabled;
8153a75d378SCorentin Chary 	return sabi_command(samsung, commands->set_usb_charge,
8163a75d378SCorentin Chary 			    &data, NULL);
8173a75d378SCorentin Chary }
8183a75d378SCorentin Chary 
8193a75d378SCorentin Chary static ssize_t get_usb_charge(struct device *dev,
8203a75d378SCorentin Chary 			      struct device_attribute *attr,
8213a75d378SCorentin Chary 			      char *buf)
8223a75d378SCorentin Chary {
8233a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
8243a75d378SCorentin Chary 	int ret;
8253a75d378SCorentin Chary 
8263a75d378SCorentin Chary 	ret = read_usb_charge(samsung);
8273a75d378SCorentin Chary 	if (ret < 0)
8283a75d378SCorentin Chary 		return ret;
8293a75d378SCorentin Chary 
8303a75d378SCorentin Chary 	return sprintf(buf, "%d\n", ret);
8313a75d378SCorentin Chary }
8323a75d378SCorentin Chary 
8333a75d378SCorentin Chary static ssize_t set_usb_charge(struct device *dev,
8343a75d378SCorentin Chary 			      struct device_attribute *attr,
8353a75d378SCorentin Chary 			      const char *buf, size_t count)
8363a75d378SCorentin Chary {
8373a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
8383a75d378SCorentin Chary 	int ret, value;
8393a75d378SCorentin Chary 
840802cf2e1SDarren Hart 	if (!count || kstrtoint(buf, 0, &value) != 0)
8413a75d378SCorentin Chary 		return -EINVAL;
8423a75d378SCorentin Chary 
8433a75d378SCorentin Chary 	ret = write_usb_charge(samsung, !!value);
8443a75d378SCorentin Chary 	if (ret < 0)
8453a75d378SCorentin Chary 		return ret;
8463a75d378SCorentin Chary 
8473a75d378SCorentin Chary 	return count;
8483a75d378SCorentin Chary }
8493a75d378SCorentin Chary 
8503a75d378SCorentin Chary static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
8513a75d378SCorentin Chary 		   get_usb_charge, set_usb_charge);
8523a75d378SCorentin Chary 
853b0dcaf4fSJulijonas Kikutis static int read_lid_handling(struct samsung_laptop *samsung)
854b0dcaf4fSJulijonas Kikutis {
855b0dcaf4fSJulijonas Kikutis 	const struct sabi_commands *commands = &samsung->config->commands;
856b0dcaf4fSJulijonas Kikutis 	struct sabi_data data;
857b0dcaf4fSJulijonas Kikutis 	int retval;
858b0dcaf4fSJulijonas Kikutis 
859b0dcaf4fSJulijonas Kikutis 	if (commands->get_lid_handling == 0xFFFF)
860b0dcaf4fSJulijonas Kikutis 		return -ENODEV;
861b0dcaf4fSJulijonas Kikutis 
862b0dcaf4fSJulijonas Kikutis 	memset(&data, 0, sizeof(data));
863b0dcaf4fSJulijonas Kikutis 	retval = sabi_command(samsung, commands->get_lid_handling,
864b0dcaf4fSJulijonas Kikutis 			      &data, &data);
865b0dcaf4fSJulijonas Kikutis 
866b0dcaf4fSJulijonas Kikutis 	if (retval)
867b0dcaf4fSJulijonas Kikutis 		return retval;
868b0dcaf4fSJulijonas Kikutis 
869b0dcaf4fSJulijonas Kikutis 	return data.data[0] & 0x1;
870b0dcaf4fSJulijonas Kikutis }
871b0dcaf4fSJulijonas Kikutis 
872b0dcaf4fSJulijonas Kikutis static int write_lid_handling(struct samsung_laptop *samsung,
873b0dcaf4fSJulijonas Kikutis 			      int enabled)
874b0dcaf4fSJulijonas Kikutis {
875b0dcaf4fSJulijonas Kikutis 	const struct sabi_commands *commands = &samsung->config->commands;
876b0dcaf4fSJulijonas Kikutis 	struct sabi_data data;
877b0dcaf4fSJulijonas Kikutis 
878b0dcaf4fSJulijonas Kikutis 	memset(&data, 0, sizeof(data));
879b0dcaf4fSJulijonas Kikutis 	data.data[0] = 0x80 | enabled;
880b0dcaf4fSJulijonas Kikutis 	return sabi_command(samsung, commands->set_lid_handling,
881b0dcaf4fSJulijonas Kikutis 			    &data, NULL);
882b0dcaf4fSJulijonas Kikutis }
883b0dcaf4fSJulijonas Kikutis 
884b0dcaf4fSJulijonas Kikutis static ssize_t get_lid_handling(struct device *dev,
885b0dcaf4fSJulijonas Kikutis 				struct device_attribute *attr,
886b0dcaf4fSJulijonas Kikutis 				char *buf)
887b0dcaf4fSJulijonas Kikutis {
888b0dcaf4fSJulijonas Kikutis 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
889b0dcaf4fSJulijonas Kikutis 	int ret;
890b0dcaf4fSJulijonas Kikutis 
891b0dcaf4fSJulijonas Kikutis 	ret = read_lid_handling(samsung);
892b0dcaf4fSJulijonas Kikutis 	if (ret < 0)
893b0dcaf4fSJulijonas Kikutis 		return ret;
894b0dcaf4fSJulijonas Kikutis 
895b0dcaf4fSJulijonas Kikutis 	return sprintf(buf, "%d\n", ret);
896b0dcaf4fSJulijonas Kikutis }
897b0dcaf4fSJulijonas Kikutis 
898b0dcaf4fSJulijonas Kikutis static ssize_t set_lid_handling(struct device *dev,
899b0dcaf4fSJulijonas Kikutis 				struct device_attribute *attr,
900b0dcaf4fSJulijonas Kikutis 				const char *buf, size_t count)
901b0dcaf4fSJulijonas Kikutis {
902b0dcaf4fSJulijonas Kikutis 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
903b0dcaf4fSJulijonas Kikutis 	int ret, value;
904b0dcaf4fSJulijonas Kikutis 
905b0dcaf4fSJulijonas Kikutis 	if (!count || kstrtoint(buf, 0, &value) != 0)
906b0dcaf4fSJulijonas Kikutis 		return -EINVAL;
907b0dcaf4fSJulijonas Kikutis 
908b0dcaf4fSJulijonas Kikutis 	ret = write_lid_handling(samsung, !!value);
909b0dcaf4fSJulijonas Kikutis 	if (ret < 0)
910b0dcaf4fSJulijonas Kikutis 		return ret;
911b0dcaf4fSJulijonas Kikutis 
912b0dcaf4fSJulijonas Kikutis 	return count;
913b0dcaf4fSJulijonas Kikutis }
914b0dcaf4fSJulijonas Kikutis 
915b0dcaf4fSJulijonas Kikutis static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO,
916b0dcaf4fSJulijonas Kikutis 		   get_lid_handling, set_lid_handling);
917b0dcaf4fSJulijonas Kikutis 
918a66c1662SCorentin Chary static struct attribute *platform_attributes[] = {
919a66c1662SCorentin Chary 	&dev_attr_performance_level.attr,
920cb5b5c91SCorentin Chary 	&dev_attr_battery_life_extender.attr,
9213a75d378SCorentin Chary 	&dev_attr_usb_charge.attr,
922b0dcaf4fSJulijonas Kikutis 	&dev_attr_lid_handling.attr,
923a66c1662SCorentin Chary 	NULL
924a66c1662SCorentin Chary };
9252d70b73aSGreg Kroah-Hartman 
9265dea7a20SCorentin Chary static int find_signature(void __iomem *memcheck, const char *testStr)
9275dea7a20SCorentin Chary {
9285dea7a20SCorentin Chary 	int i = 0;
9295dea7a20SCorentin Chary 	int loca;
9305dea7a20SCorentin Chary 
9315dea7a20SCorentin Chary 	for (loca = 0; loca < 0xffff; loca++) {
9325dea7a20SCorentin Chary 		char temp = readb(memcheck + loca);
9335dea7a20SCorentin Chary 
9345dea7a20SCorentin Chary 		if (temp == testStr[i]) {
9355dea7a20SCorentin Chary 			if (i == strlen(testStr)-1)
9365dea7a20SCorentin Chary 				break;
9375dea7a20SCorentin Chary 			++i;
9385dea7a20SCorentin Chary 		} else {
9395dea7a20SCorentin Chary 			i = 0;
9405dea7a20SCorentin Chary 		}
9415dea7a20SCorentin Chary 	}
9425dea7a20SCorentin Chary 	return loca;
9435dea7a20SCorentin Chary }
9445dea7a20SCorentin Chary 
9455dea7a20SCorentin Chary static void samsung_rfkill_exit(struct samsung_laptop *samsung)
9465dea7a20SCorentin Chary {
94784d482f2SCorentin Chary 	if (samsung->wlan.rfkill) {
94884d482f2SCorentin Chary 		rfkill_unregister(samsung->wlan.rfkill);
94984d482f2SCorentin Chary 		rfkill_destroy(samsung->wlan.rfkill);
95084d482f2SCorentin Chary 		samsung->wlan.rfkill = NULL;
9515dea7a20SCorentin Chary 	}
95284d482f2SCorentin Chary 	if (samsung->bluetooth.rfkill) {
95384d482f2SCorentin Chary 		rfkill_unregister(samsung->bluetooth.rfkill);
95484d482f2SCorentin Chary 		rfkill_destroy(samsung->bluetooth.rfkill);
95584d482f2SCorentin Chary 		samsung->bluetooth.rfkill = NULL;
95684d482f2SCorentin Chary 	}
95784d482f2SCorentin Chary }
95884d482f2SCorentin Chary 
95984d482f2SCorentin Chary static int samsung_new_rfkill(struct samsung_laptop *samsung,
96084d482f2SCorentin Chary 			      struct samsung_rfkill *arfkill,
96184d482f2SCorentin Chary 			      const char *name, enum rfkill_type type,
96284d482f2SCorentin Chary 			      const struct rfkill_ops *ops,
96384d482f2SCorentin Chary 			      int blocked)
96484d482f2SCorentin Chary {
96584d482f2SCorentin Chary 	struct rfkill **rfkill = &arfkill->rfkill;
96684d482f2SCorentin Chary 	int ret;
96784d482f2SCorentin Chary 
96884d482f2SCorentin Chary 	arfkill->type = type;
96984d482f2SCorentin Chary 	arfkill->samsung = samsung;
97084d482f2SCorentin Chary 
97184d482f2SCorentin Chary 	*rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
97284d482f2SCorentin Chary 			       type, ops, arfkill);
97384d482f2SCorentin Chary 
97484d482f2SCorentin Chary 	if (!*rfkill)
97584d482f2SCorentin Chary 		return -EINVAL;
97684d482f2SCorentin Chary 
97784d482f2SCorentin Chary 	if (blocked != -1)
97884d482f2SCorentin Chary 		rfkill_init_sw_state(*rfkill, blocked);
97984d482f2SCorentin Chary 
98084d482f2SCorentin Chary 	ret = rfkill_register(*rfkill);
98184d482f2SCorentin Chary 	if (ret) {
98284d482f2SCorentin Chary 		rfkill_destroy(*rfkill);
98384d482f2SCorentin Chary 		*rfkill = NULL;
98484d482f2SCorentin Chary 		return ret;
98584d482f2SCorentin Chary 	}
98684d482f2SCorentin Chary 	return 0;
98784d482f2SCorentin Chary }
98884d482f2SCorentin Chary 
98984d482f2SCorentin Chary static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
99084d482f2SCorentin Chary {
99184d482f2SCorentin Chary 	return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
99284d482f2SCorentin Chary 				  RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
99384d482f2SCorentin Chary }
99484d482f2SCorentin Chary 
99584d482f2SCorentin Chary static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
99684d482f2SCorentin Chary {
99784d482f2SCorentin Chary 	struct sabi_data data;
99884d482f2SCorentin Chary 	int ret;
99984d482f2SCorentin Chary 
100084d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
100120db88e3SCorentin Chary 	if (ret) {
100220db88e3SCorentin Chary 		/* Some swsmi laptops use the old seclinux way to control
100320db88e3SCorentin Chary 		 * wireless devices */
100420db88e3SCorentin Chary 		if (ret == -EINVAL)
100520db88e3SCorentin Chary 			ret = samsung_rfkill_init_seclinux(samsung);
100684d482f2SCorentin Chary 		return ret;
100720db88e3SCorentin Chary 	}
100884d482f2SCorentin Chary 
100984d482f2SCorentin Chary 	/* 0x02 seems to mean that the device is no present/available */
101084d482f2SCorentin Chary 
101184d482f2SCorentin Chary 	if (data.data[WL_STATUS_WLAN] != 0x02)
101284d482f2SCorentin Chary 		ret = samsung_new_rfkill(samsung, &samsung->wlan,
101384d482f2SCorentin Chary 					 "samsung-wlan",
101484d482f2SCorentin Chary 					 RFKILL_TYPE_WLAN,
101584d482f2SCorentin Chary 					 &swsmi_rfkill_ops,
101684d482f2SCorentin Chary 					 !data.data[WL_STATUS_WLAN]);
101784d482f2SCorentin Chary 	if (ret)
101884d482f2SCorentin Chary 		goto exit;
101984d482f2SCorentin Chary 
102084d482f2SCorentin Chary 	if (data.data[WL_STATUS_BT] != 0x02)
102184d482f2SCorentin Chary 		ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
102284d482f2SCorentin Chary 					 "samsung-bluetooth",
102384d482f2SCorentin Chary 					 RFKILL_TYPE_BLUETOOTH,
102484d482f2SCorentin Chary 					 &swsmi_rfkill_ops,
102584d482f2SCorentin Chary 					 !data.data[WL_STATUS_BT]);
102684d482f2SCorentin Chary 	if (ret)
102784d482f2SCorentin Chary 		goto exit;
102884d482f2SCorentin Chary 
102984d482f2SCorentin Chary exit:
103084d482f2SCorentin Chary 	if (ret)
103184d482f2SCorentin Chary 		samsung_rfkill_exit(samsung);
103284d482f2SCorentin Chary 
103384d482f2SCorentin Chary 	return ret;
10345dea7a20SCorentin Chary }
10355dea7a20SCorentin Chary 
10365dea7a20SCorentin Chary static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
10375dea7a20SCorentin Chary {
103884d482f2SCorentin Chary 	if (samsung->config->sabi_version == 2)
103984d482f2SCorentin Chary 		return samsung_rfkill_init_seclinux(samsung);
104084d482f2SCorentin Chary 	if (samsung->config->sabi_version == 3)
104184d482f2SCorentin Chary 		return samsung_rfkill_init_swsmi(samsung);
10425dea7a20SCorentin Chary 	return 0;
10435dea7a20SCorentin Chary }
10445dea7a20SCorentin Chary 
1045b0dcaf4fSJulijonas Kikutis static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
1046b0dcaf4fSJulijonas Kikutis {
1047b0dcaf4fSJulijonas Kikutis 	if (samsung->quirks->lid_handling)
1048b0dcaf4fSJulijonas Kikutis 		write_lid_handling(samsung, 0);
1049b0dcaf4fSJulijonas Kikutis }
1050b0dcaf4fSJulijonas Kikutis 
1051b0dcaf4fSJulijonas Kikutis static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
1052b0dcaf4fSJulijonas Kikutis {
1053b0dcaf4fSJulijonas Kikutis 	int retval = 0;
1054b0dcaf4fSJulijonas Kikutis 
1055b0dcaf4fSJulijonas Kikutis 	if (samsung->quirks->lid_handling)
1056b0dcaf4fSJulijonas Kikutis 		retval = write_lid_handling(samsung, 1);
1057b0dcaf4fSJulijonas Kikutis 
1058b0dcaf4fSJulijonas Kikutis 	return retval;
1059b0dcaf4fSJulijonas Kikutis }
1060b0dcaf4fSJulijonas Kikutis 
1061f674ebf1SCorentin Chary static int kbd_backlight_enable(struct samsung_laptop *samsung)
1062f674ebf1SCorentin Chary {
1063f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
1064f674ebf1SCorentin Chary 	struct sabi_data data;
1065f674ebf1SCorentin Chary 	int retval;
1066f674ebf1SCorentin Chary 
1067f674ebf1SCorentin Chary 	if (commands->kbd_backlight == 0xFFFF)
1068f674ebf1SCorentin Chary 		return -ENODEV;
1069f674ebf1SCorentin Chary 
1070f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
1071f674ebf1SCorentin Chary 	data.d0 = 0xaabb;
1072f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
1073f674ebf1SCorentin Chary 			      &data, &data);
1074f674ebf1SCorentin Chary 
1075f674ebf1SCorentin Chary 	if (retval)
1076f674ebf1SCorentin Chary 		return retval;
1077f674ebf1SCorentin Chary 
1078f674ebf1SCorentin Chary 	if (data.d0 != 0xccdd)
1079f674ebf1SCorentin Chary 		return -ENODEV;
1080f674ebf1SCorentin Chary 	return 0;
1081f674ebf1SCorentin Chary }
1082f674ebf1SCorentin Chary 
1083f674ebf1SCorentin Chary static int kbd_backlight_read(struct samsung_laptop *samsung)
1084f674ebf1SCorentin Chary {
1085f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
1086f674ebf1SCorentin Chary 	struct sabi_data data;
1087f674ebf1SCorentin Chary 	int retval;
1088f674ebf1SCorentin Chary 
1089f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
1090f674ebf1SCorentin Chary 	data.data[0] = 0x81;
1091f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
1092f674ebf1SCorentin Chary 			      &data, &data);
1093f674ebf1SCorentin Chary 
1094f674ebf1SCorentin Chary 	if (retval)
1095f674ebf1SCorentin Chary 		return retval;
1096f674ebf1SCorentin Chary 
1097f674ebf1SCorentin Chary 	return data.data[0];
1098f674ebf1SCorentin Chary }
1099f674ebf1SCorentin Chary 
1100f674ebf1SCorentin Chary static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1101f674ebf1SCorentin Chary {
1102f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
1103f674ebf1SCorentin Chary 	struct sabi_data data;
1104f674ebf1SCorentin Chary 
1105f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
1106f674ebf1SCorentin Chary 	data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1107f674ebf1SCorentin Chary 	return sabi_command(samsung, commands->kbd_backlight,
1108f674ebf1SCorentin Chary 			    &data, NULL);
1109f674ebf1SCorentin Chary }
1110f674ebf1SCorentin Chary 
1111f674ebf1SCorentin Chary static void kbd_led_update(struct work_struct *work)
1112f674ebf1SCorentin Chary {
1113f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1114f674ebf1SCorentin Chary 
1115f674ebf1SCorentin Chary 	samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1116f674ebf1SCorentin Chary 	kbd_backlight_write(samsung, samsung->kbd_led_wk);
1117f674ebf1SCorentin Chary }
1118f674ebf1SCorentin Chary 
1119f674ebf1SCorentin Chary static void kbd_led_set(struct led_classdev *led_cdev,
1120f674ebf1SCorentin Chary 			enum led_brightness value)
1121f674ebf1SCorentin Chary {
1122f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1123f674ebf1SCorentin Chary 
1124f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1125f674ebf1SCorentin Chary 
1126f674ebf1SCorentin Chary 	if (value > samsung->kbd_led.max_brightness)
1127f674ebf1SCorentin Chary 		value = samsung->kbd_led.max_brightness;
1128f674ebf1SCorentin Chary 	else if (value < 0)
1129f674ebf1SCorentin Chary 		value = 0;
1130f674ebf1SCorentin Chary 
1131f674ebf1SCorentin Chary 	samsung->kbd_led_wk = value;
1132f674ebf1SCorentin Chary 	queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1133f674ebf1SCorentin Chary }
1134f674ebf1SCorentin Chary 
1135f674ebf1SCorentin Chary static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1136f674ebf1SCorentin Chary {
1137f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1138f674ebf1SCorentin Chary 
1139f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1140f674ebf1SCorentin Chary 	return kbd_backlight_read(samsung);
1141f674ebf1SCorentin Chary }
1142f674ebf1SCorentin Chary 
1143f674ebf1SCorentin Chary static void samsung_leds_exit(struct samsung_laptop *samsung)
1144f674ebf1SCorentin Chary {
1145f674ebf1SCorentin Chary 	if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
1146f674ebf1SCorentin Chary 		led_classdev_unregister(&samsung->kbd_led);
1147f674ebf1SCorentin Chary 	if (samsung->led_workqueue)
1148f674ebf1SCorentin Chary 		destroy_workqueue(samsung->led_workqueue);
1149f674ebf1SCorentin Chary }
1150f674ebf1SCorentin Chary 
1151f674ebf1SCorentin Chary static int __init samsung_leds_init(struct samsung_laptop *samsung)
1152f674ebf1SCorentin Chary {
1153f674ebf1SCorentin Chary 	int ret = 0;
1154f674ebf1SCorentin Chary 
1155f674ebf1SCorentin Chary 	samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1156f674ebf1SCorentin Chary 	if (!samsung->led_workqueue)
1157f674ebf1SCorentin Chary 		return -ENOMEM;
1158f674ebf1SCorentin Chary 
1159f674ebf1SCorentin Chary 	if (kbd_backlight_enable(samsung) >= 0) {
1160f674ebf1SCorentin Chary 		INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1161f674ebf1SCorentin Chary 
1162f674ebf1SCorentin Chary 		samsung->kbd_led.name = "samsung::kbd_backlight";
1163f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_set = kbd_led_set;
1164f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_get = kbd_led_get;
1165f674ebf1SCorentin Chary 		samsung->kbd_led.max_brightness = 8;
11660ca849eaSScott Thrasher 		if (samsung->quirks->four_kbd_backlight_levels)
11670ca849eaSScott Thrasher 			samsung->kbd_led.max_brightness = 4;
1168f674ebf1SCorentin Chary 
1169f674ebf1SCorentin Chary 		ret = led_classdev_register(&samsung->platform_device->dev,
1170f674ebf1SCorentin Chary 					   &samsung->kbd_led);
1171f674ebf1SCorentin Chary 	}
1172f674ebf1SCorentin Chary 
1173f674ebf1SCorentin Chary 	if (ret)
1174f674ebf1SCorentin Chary 		samsung_leds_exit(samsung);
1175f674ebf1SCorentin Chary 
1176f674ebf1SCorentin Chary 	return ret;
1177f674ebf1SCorentin Chary }
1178f674ebf1SCorentin Chary 
11795dea7a20SCorentin Chary static void samsung_backlight_exit(struct samsung_laptop *samsung)
11805dea7a20SCorentin Chary {
11815dea7a20SCorentin Chary 	if (samsung->backlight_device) {
11825dea7a20SCorentin Chary 		backlight_device_unregister(samsung->backlight_device);
11835dea7a20SCorentin Chary 		samsung->backlight_device = NULL;
11845dea7a20SCorentin Chary 	}
11855dea7a20SCorentin Chary }
11865dea7a20SCorentin Chary 
11875dea7a20SCorentin Chary static int __init samsung_backlight_init(struct samsung_laptop *samsung)
11885dea7a20SCorentin Chary {
11895dea7a20SCorentin Chary 	struct backlight_device *bd;
11905dea7a20SCorentin Chary 	struct backlight_properties props;
11915dea7a20SCorentin Chary 
1192f34cd9caSCorentin Chary 	if (!samsung->handle_backlight)
1193f34cd9caSCorentin Chary 		return 0;
1194f34cd9caSCorentin Chary 
11955dea7a20SCorentin Chary 	memset(&props, 0, sizeof(struct backlight_properties));
11965dea7a20SCorentin Chary 	props.type = BACKLIGHT_PLATFORM;
11975dea7a20SCorentin Chary 	props.max_brightness = samsung->config->max_brightness -
11985dea7a20SCorentin Chary 		samsung->config->min_brightness;
11995dea7a20SCorentin Chary 
12005dea7a20SCorentin Chary 	bd = backlight_device_register("samsung",
12015dea7a20SCorentin Chary 				       &samsung->platform_device->dev,
12025dea7a20SCorentin Chary 				       samsung, &backlight_ops,
12035dea7a20SCorentin Chary 				       &props);
12045dea7a20SCorentin Chary 	if (IS_ERR(bd))
12055dea7a20SCorentin Chary 		return PTR_ERR(bd);
12065dea7a20SCorentin Chary 
12075dea7a20SCorentin Chary 	samsung->backlight_device = bd;
12085dea7a20SCorentin Chary 	samsung->backlight_device->props.brightness = read_brightness(samsung);
12095dea7a20SCorentin Chary 	samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
12105dea7a20SCorentin Chary 	backlight_update_status(samsung->backlight_device);
12115dea7a20SCorentin Chary 
12125dea7a20SCorentin Chary 	return 0;
12135dea7a20SCorentin Chary }
12145dea7a20SCorentin Chary 
1215bde9e509SDan Carpenter static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1216a66c1662SCorentin Chary 					struct attribute *attr, int idx)
1217a66c1662SCorentin Chary {
1218a66c1662SCorentin Chary 	struct device *dev = container_of(kobj, struct device, kobj);
1219d605ca29SWolfram Sang 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
1220a66c1662SCorentin Chary 	bool ok = true;
1221a66c1662SCorentin Chary 
1222a66c1662SCorentin Chary 	if (attr == &dev_attr_performance_level.attr)
1223a66c1662SCorentin Chary 		ok = !!samsung->config->performance_levels[0].name;
1224cb5b5c91SCorentin Chary 	if (attr == &dev_attr_battery_life_extender.attr)
1225cb5b5c91SCorentin Chary 		ok = !!(read_battery_life_extender(samsung) >= 0);
12263a75d378SCorentin Chary 	if (attr == &dev_attr_usb_charge.attr)
12273a75d378SCorentin Chary 		ok = !!(read_usb_charge(samsung) >= 0);
1228b0dcaf4fSJulijonas Kikutis 	if (attr == &dev_attr_lid_handling.attr)
1229b0dcaf4fSJulijonas Kikutis 		ok = !!(read_lid_handling(samsung) >= 0);
1230a66c1662SCorentin Chary 
1231a66c1662SCorentin Chary 	return ok ? attr->mode : 0;
1232a66c1662SCorentin Chary }
1233a66c1662SCorentin Chary 
12348546268eSArvind Yadav static const struct attribute_group platform_attribute_group = {
1235a66c1662SCorentin Chary 	.is_visible = samsung_sysfs_is_visible,
1236a66c1662SCorentin Chary 	.attrs = platform_attributes
1237a66c1662SCorentin Chary };
1238a66c1662SCorentin Chary 
12395dea7a20SCorentin Chary static void samsung_sysfs_exit(struct samsung_laptop *samsung)
12405dea7a20SCorentin Chary {
1241a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
1242a66c1662SCorentin Chary 
1243a66c1662SCorentin Chary 	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
12445dea7a20SCorentin Chary }
12455dea7a20SCorentin Chary 
12465dea7a20SCorentin Chary static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
12475dea7a20SCorentin Chary {
1248a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
1249a66c1662SCorentin Chary 
1250a66c1662SCorentin Chary 	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1251a66c1662SCorentin Chary 
12525dea7a20SCorentin Chary }
12535dea7a20SCorentin Chary 
12549a7c551bSAndy Shevchenko static int samsung_laptop_call_show(struct seq_file *m, void *data)
12555b80fc40SCorentin Chary {
12565b80fc40SCorentin Chary 	struct samsung_laptop *samsung = m->private;
12575b80fc40SCorentin Chary 	struct sabi_data *sdata = &samsung->debug.data;
12585b80fc40SCorentin Chary 	int ret;
12595b80fc40SCorentin Chary 
12605b80fc40SCorentin Chary 	seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
12615b80fc40SCorentin Chary 		   samsung->debug.command,
12625b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
12635b80fc40SCorentin Chary 
12645b80fc40SCorentin Chary 	ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
12655b80fc40SCorentin Chary 
12665b80fc40SCorentin Chary 	if (ret) {
12675b80fc40SCorentin Chary 		seq_printf(m, "SABI command 0x%04x failed\n",
12685b80fc40SCorentin Chary 			   samsung->debug.command);
12695b80fc40SCorentin Chary 		return ret;
12705b80fc40SCorentin Chary 	}
12715b80fc40SCorentin Chary 
12725b80fc40SCorentin Chary 	seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
12735b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
12745b80fc40SCorentin Chary 	return 0;
12755b80fc40SCorentin Chary }
12769a7c551bSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(samsung_laptop_call);
12775b80fc40SCorentin Chary 
12785b80fc40SCorentin Chary static void samsung_debugfs_exit(struct samsung_laptop *samsung)
12795b80fc40SCorentin Chary {
12805b80fc40SCorentin Chary 	debugfs_remove_recursive(samsung->debug.root);
12815b80fc40SCorentin Chary }
12825b80fc40SCorentin Chary 
12835b80fc40SCorentin Chary static int samsung_debugfs_init(struct samsung_laptop *samsung)
12845b80fc40SCorentin Chary {
12855b80fc40SCorentin Chary 	struct dentry *dent;
12865b80fc40SCorentin Chary 
12875b80fc40SCorentin Chary 	samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
12885b80fc40SCorentin Chary 	if (!samsung->debug.root) {
12895b80fc40SCorentin Chary 		pr_err("failed to create debugfs directory");
12905b80fc40SCorentin Chary 		goto error_debugfs;
12915b80fc40SCorentin Chary 	}
12925b80fc40SCorentin Chary 
12935b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
12945b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.size = 0xffff;
12955b80fc40SCorentin Chary 
12965b80fc40SCorentin Chary 	samsung->debug.data_wrapper.data = &samsung->debug.data;
12975b80fc40SCorentin Chary 	samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
12985b80fc40SCorentin Chary 
12996f6ae06eSCorentin Chary 	samsung->debug.sdiag_wrapper.data = samsung->sdiag;
13006f6ae06eSCorentin Chary 	samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
13016f6ae06eSCorentin Chary 
13025b80fc40SCorentin Chary 	dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
13035b80fc40SCorentin Chary 				  samsung->debug.root, &samsung->debug.command);
13045b80fc40SCorentin Chary 	if (!dent)
13055b80fc40SCorentin Chary 		goto error_debugfs;
13065b80fc40SCorentin Chary 
13075b80fc40SCorentin Chary 	dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
13085b80fc40SCorentin Chary 				  &samsung->debug.data.d0);
13095b80fc40SCorentin Chary 	if (!dent)
13105b80fc40SCorentin Chary 		goto error_debugfs;
13115b80fc40SCorentin Chary 
13125b80fc40SCorentin Chary 	dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
13135b80fc40SCorentin Chary 				  &samsung->debug.data.d1);
13145b80fc40SCorentin Chary 	if (!dent)
13155b80fc40SCorentin Chary 		goto error_debugfs;
13165b80fc40SCorentin Chary 
13175b80fc40SCorentin Chary 	dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
13185b80fc40SCorentin Chary 				  &samsung->debug.data.d2);
13195b80fc40SCorentin Chary 	if (!dent)
13205b80fc40SCorentin Chary 		goto error_debugfs;
13215b80fc40SCorentin Chary 
13225b80fc40SCorentin Chary 	dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
13235b80fc40SCorentin Chary 				 &samsung->debug.data.d3);
13245b80fc40SCorentin Chary 	if (!dent)
13255b80fc40SCorentin Chary 		goto error_debugfs;
13265b80fc40SCorentin Chary 
13275b80fc40SCorentin Chary 	dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
13285b80fc40SCorentin Chary 				   samsung->debug.root,
13295b80fc40SCorentin Chary 				   &samsung->debug.data_wrapper);
13305b80fc40SCorentin Chary 	if (!dent)
13315b80fc40SCorentin Chary 		goto error_debugfs;
13325b80fc40SCorentin Chary 
13335b80fc40SCorentin Chary 	dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
13345b80fc40SCorentin Chary 				   samsung->debug.root,
13355b80fc40SCorentin Chary 				   &samsung->debug.f0000_wrapper);
13365b80fc40SCorentin Chary 	if (!dent)
13375b80fc40SCorentin Chary 		goto error_debugfs;
13385b80fc40SCorentin Chary 
13395b80fc40SCorentin Chary 	dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
13405b80fc40SCorentin Chary 				   samsung->debug.root, samsung,
13419a7c551bSAndy Shevchenko 				   &samsung_laptop_call_fops);
13425b80fc40SCorentin Chary 	if (!dent)
13435b80fc40SCorentin Chary 		goto error_debugfs;
13445b80fc40SCorentin Chary 
13456f6ae06eSCorentin Chary 	dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
13466f6ae06eSCorentin Chary 				   samsung->debug.root,
13476f6ae06eSCorentin Chary 				   &samsung->debug.sdiag_wrapper);
13486f6ae06eSCorentin Chary 	if (!dent)
13496f6ae06eSCorentin Chary 		goto error_debugfs;
13506f6ae06eSCorentin Chary 
13515b80fc40SCorentin Chary 	return 0;
13525b80fc40SCorentin Chary 
13535b80fc40SCorentin Chary error_debugfs:
13545b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
13555b80fc40SCorentin Chary 	return -ENOMEM;
13565b80fc40SCorentin Chary }
13575b80fc40SCorentin Chary 
13585dea7a20SCorentin Chary static void samsung_sabi_exit(struct samsung_laptop *samsung)
13595dea7a20SCorentin Chary {
13605dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
13615dea7a20SCorentin Chary 
13625dea7a20SCorentin Chary 	/* Turn off "Linux" mode in the BIOS */
13635dea7a20SCorentin Chary 	if (config && config->commands.set_linux != 0xff)
13647e960711SCorentin Chary 		sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
13655dea7a20SCorentin Chary 
13665dea7a20SCorentin Chary 	if (samsung->sabi_iface) {
13675dea7a20SCorentin Chary 		iounmap(samsung->sabi_iface);
13685dea7a20SCorentin Chary 		samsung->sabi_iface = NULL;
13695dea7a20SCorentin Chary 	}
13705dea7a20SCorentin Chary 	if (samsung->f0000_segment) {
13715dea7a20SCorentin Chary 		iounmap(samsung->f0000_segment);
13725dea7a20SCorentin Chary 		samsung->f0000_segment = NULL;
13735dea7a20SCorentin Chary 	}
13745dea7a20SCorentin Chary 
13755dea7a20SCorentin Chary 	samsung->config = NULL;
13765dea7a20SCorentin Chary }
13775dea7a20SCorentin Chary 
137849dd7730SCorentin Chary static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
137949dd7730SCorentin Chary 				      unsigned int ifaceP)
13805dea7a20SCorentin Chary {
13815dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
13825dea7a20SCorentin Chary 
13835dea7a20SCorentin Chary 	printk(KERN_DEBUG "This computer supports SABI==%x\n",
13845dea7a20SCorentin Chary 	       loca + 0xf0000 - 6);
138549dd7730SCorentin Chary 
13865dea7a20SCorentin Chary 	printk(KERN_DEBUG "SABI header:\n");
13875dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
13885dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.port));
13895dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
13905dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.iface_func));
13915dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
13925dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.en_mem));
13935dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
13945dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.re_mem));
13955dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
13965dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_offset));
13975dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
13985dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_segment));
13995dea7a20SCorentin Chary 
140049dd7730SCorentin Chary 	printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
14015dea7a20SCorentin Chary }
14025dea7a20SCorentin Chary 
14036f6ae06eSCorentin Chary static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
14046f6ae06eSCorentin Chary {
14056f6ae06eSCorentin Chary 	int loca = find_signature(samsung->f0000_segment, "SDiaG@");
14066f6ae06eSCorentin Chary 	int i;
14076f6ae06eSCorentin Chary 
14086f6ae06eSCorentin Chary 	if (loca == 0xffff)
14096f6ae06eSCorentin Chary 		return ;
14106f6ae06eSCorentin Chary 
14116f6ae06eSCorentin Chary 	/* Example:
14126f6ae06eSCorentin Chary 	 * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
14136f6ae06eSCorentin Chary 	 *
14146f6ae06eSCorentin Chary 	 * Product name: 90X3A
14156f6ae06eSCorentin Chary 	 * BIOS Version: 07HL
14166f6ae06eSCorentin Chary 	 */
14176f6ae06eSCorentin Chary 	loca += 1;
14186f6ae06eSCorentin Chary 	for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
14196f6ae06eSCorentin Chary 		char temp = readb(samsung->f0000_segment + loca);
14206f6ae06eSCorentin Chary 
14216f6ae06eSCorentin Chary 		if (isalnum(temp) || temp == '/' || temp == '-')
14226f6ae06eSCorentin Chary 			samsung->sdiag[i++] = temp;
14236f6ae06eSCorentin Chary 		else
14246f6ae06eSCorentin Chary 			break ;
14256f6ae06eSCorentin Chary 	}
14266f6ae06eSCorentin Chary 
14276f6ae06eSCorentin Chary 	if (debug && samsung->sdiag[0])
14286f6ae06eSCorentin Chary 		pr_info("sdiag: %s", samsung->sdiag);
14296f6ae06eSCorentin Chary }
14306f6ae06eSCorentin Chary 
14315dea7a20SCorentin Chary static int __init samsung_sabi_init(struct samsung_laptop *samsung)
14325dea7a20SCorentin Chary {
14335dea7a20SCorentin Chary 	const struct sabi_config *config = NULL;
14345dea7a20SCorentin Chary 	const struct sabi_commands *commands;
14355dea7a20SCorentin Chary 	unsigned int ifaceP;
14360d2c9535SAndy Shevchenko 	int loca = 0xffff;
14375dea7a20SCorentin Chary 	int ret = 0;
14385dea7a20SCorentin Chary 	int i;
14395dea7a20SCorentin Chary 
14405dea7a20SCorentin Chary 	samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
14415dea7a20SCorentin Chary 	if (!samsung->f0000_segment) {
14423be324a9SCorentin Chary 		if (debug || force)
14435dea7a20SCorentin Chary 			pr_err("Can't map the segment at 0xf0000\n");
14445dea7a20SCorentin Chary 		ret = -EINVAL;
14455dea7a20SCorentin Chary 		goto exit;
14465dea7a20SCorentin Chary 	}
14475dea7a20SCorentin Chary 
14486f6ae06eSCorentin Chary 	samsung_sabi_diag(samsung);
14496f6ae06eSCorentin Chary 
14505dea7a20SCorentin Chary 	/* Try to find one of the signatures in memory to find the header */
14514e7f09adSLad, Prabhakar 	for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
14525dea7a20SCorentin Chary 		samsung->config = &sabi_configs[i];
14535dea7a20SCorentin Chary 		loca = find_signature(samsung->f0000_segment,
14545dea7a20SCorentin Chary 				      samsung->config->test_string);
14555dea7a20SCorentin Chary 		if (loca != 0xffff)
14565dea7a20SCorentin Chary 			break;
14575dea7a20SCorentin Chary 	}
14585dea7a20SCorentin Chary 
14595dea7a20SCorentin Chary 	if (loca == 0xffff) {
14603be324a9SCorentin Chary 		if (debug || force)
14615dea7a20SCorentin Chary 			pr_err("This computer does not support SABI\n");
14625dea7a20SCorentin Chary 		ret = -ENODEV;
14635dea7a20SCorentin Chary 		goto exit;
14645dea7a20SCorentin Chary 	}
14655dea7a20SCorentin Chary 
14665dea7a20SCorentin Chary 	config = samsung->config;
14675dea7a20SCorentin Chary 	commands = &config->commands;
14685dea7a20SCorentin Chary 
14695dea7a20SCorentin Chary 	/* point to the SMI port Number */
14705dea7a20SCorentin Chary 	loca += 1;
14715dea7a20SCorentin Chary 	samsung->sabi = (samsung->f0000_segment + loca);
14725dea7a20SCorentin Chary 
14735dea7a20SCorentin Chary 	/* Get a pointer to the SABI Interface */
14745dea7a20SCorentin Chary 	ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
14755dea7a20SCorentin Chary 	ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
147649dd7730SCorentin Chary 
147749dd7730SCorentin Chary 	if (debug)
147849dd7730SCorentin Chary 		samsung_sabi_infos(samsung, loca, ifaceP);
147949dd7730SCorentin Chary 
14805dea7a20SCorentin Chary 	samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
14815dea7a20SCorentin Chary 	if (!samsung->sabi_iface) {
14825dea7a20SCorentin Chary 		pr_err("Can't remap %x\n", ifaceP);
14835dea7a20SCorentin Chary 		ret = -EINVAL;
14845dea7a20SCorentin Chary 		goto exit;
14855dea7a20SCorentin Chary 	}
14865dea7a20SCorentin Chary 
14875dea7a20SCorentin Chary 	/* Turn on "Linux" mode in the BIOS */
14885dea7a20SCorentin Chary 	if (commands->set_linux != 0xff) {
14897e960711SCorentin Chary 		int retval = sabi_set_commandb(samsung,
14905dea7a20SCorentin Chary 					       commands->set_linux, 0x81);
14915dea7a20SCorentin Chary 		if (retval) {
14925dea7a20SCorentin Chary 			pr_warn("Linux mode was not set!\n");
14935dea7a20SCorentin Chary 			ret = -ENODEV;
14945dea7a20SCorentin Chary 			goto exit;
14955dea7a20SCorentin Chary 		}
14965dea7a20SCorentin Chary 	}
14975dea7a20SCorentin Chary 
14985dea7a20SCorentin Chary 	/* Check for stepping quirk */
1499f34cd9caSCorentin Chary 	if (samsung->handle_backlight)
15005dea7a20SCorentin Chary 		check_for_stepping_quirk(samsung);
15015dea7a20SCorentin Chary 
15022e777187SCorentin Chary 	pr_info("detected SABI interface: %s\n",
15032e777187SCorentin Chary 		samsung->config->test_string);
15042e777187SCorentin Chary 
15055dea7a20SCorentin Chary exit:
15065dea7a20SCorentin Chary 	if (ret)
15075dea7a20SCorentin Chary 		samsung_sabi_exit(samsung);
15085dea7a20SCorentin Chary 
15095dea7a20SCorentin Chary 	return ret;
15105dea7a20SCorentin Chary }
15115dea7a20SCorentin Chary 
15125dea7a20SCorentin Chary static void samsung_platform_exit(struct samsung_laptop *samsung)
15135dea7a20SCorentin Chary {
15145dea7a20SCorentin Chary 	if (samsung->platform_device) {
15155dea7a20SCorentin Chary 		platform_device_unregister(samsung->platform_device);
15165dea7a20SCorentin Chary 		samsung->platform_device = NULL;
15175dea7a20SCorentin Chary 	}
15185dea7a20SCorentin Chary }
15195dea7a20SCorentin Chary 
15200ca849eaSScott Thrasher static int samsung_pm_notification(struct notifier_block *nb,
15210ca849eaSScott Thrasher 				   unsigned long val, void *ptr)
15220ca849eaSScott Thrasher {
15230ca849eaSScott Thrasher 	struct samsung_laptop *samsung;
15240ca849eaSScott Thrasher 
15250ca849eaSScott Thrasher 	samsung = container_of(nb, struct samsung_laptop, pm_nb);
15260ca849eaSScott Thrasher 	if (val == PM_POST_HIBERNATION &&
15270ca849eaSScott Thrasher 	    samsung->quirks->enable_kbd_backlight)
15280ca849eaSScott Thrasher 		kbd_backlight_enable(samsung);
15290ca849eaSScott Thrasher 
1530b0dcaf4fSJulijonas Kikutis 	if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
1531b0dcaf4fSJulijonas Kikutis 		write_lid_handling(samsung, 1);
1532b0dcaf4fSJulijonas Kikutis 
15330ca849eaSScott Thrasher 	return 0;
15340ca849eaSScott Thrasher }
15350ca849eaSScott Thrasher 
15365dea7a20SCorentin Chary static int __init samsung_platform_init(struct samsung_laptop *samsung)
15375dea7a20SCorentin Chary {
15385dea7a20SCorentin Chary 	struct platform_device *pdev;
15395dea7a20SCorentin Chary 
15405dea7a20SCorentin Chary 	pdev = platform_device_register_simple("samsung", -1, NULL, 0);
15415dea7a20SCorentin Chary 	if (IS_ERR(pdev))
15425dea7a20SCorentin Chary 		return PTR_ERR(pdev);
15435dea7a20SCorentin Chary 
15445dea7a20SCorentin Chary 	samsung->platform_device = pdev;
15455dea7a20SCorentin Chary 	platform_set_drvdata(samsung->platform_device, samsung);
15465dea7a20SCorentin Chary 	return 0;
15475dea7a20SCorentin Chary }
15485dea7a20SCorentin Chary 
1549a979e2e2SCorentin Chary static struct samsung_quirks *quirks;
1550a979e2e2SCorentin Chary 
1551a979e2e2SCorentin Chary static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1552a979e2e2SCorentin Chary {
1553a979e2e2SCorentin Chary 	quirks = d->driver_data;
1554a979e2e2SCorentin Chary 	return 0;
1555a979e2e2SCorentin Chary }
1556a979e2e2SCorentin Chary 
15576faadbbbSChristoph Hellwig static const struct dmi_system_id samsung_dmi_table[] __initconst = {
15582d70b73aSGreg Kroah-Hartman 	{
15592d70b73aSGreg Kroah-Hartman 		.matches = {
15602d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
15612d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
15623be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
15632d70b73aSGreg Kroah-Hartman 		},
15642d70b73aSGreg Kroah-Hartman 	},
15652d70b73aSGreg Kroah-Hartman 	{
15662d70b73aSGreg Kroah-Hartman 		.matches = {
15672d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
15682d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
15693be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
15702d70b73aSGreg Kroah-Hartman 		},
15712d70b73aSGreg Kroah-Hartman 	},
15722d70b73aSGreg Kroah-Hartman 	{
15734e2441c0SJ Witteveen 		.matches = {
15744e2441c0SJ Witteveen 			DMI_MATCH(DMI_SYS_VENDOR,
15754e2441c0SJ Witteveen 					"SAMSUNG ELECTRONICS CO., LTD."),
15763be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
15774e2441c0SJ Witteveen 		},
15784e2441c0SJ Witteveen 	},
15794e2441c0SJ Witteveen 	{
15802d70b73aSGreg Kroah-Hartman 		.matches = {
15812d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
15822d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
15833be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
15842d70b73aSGreg Kroah-Hartman 		},
15857500eeb0STommaso Massimi 	},
1586e052067dSCorentin Chary 	/* DMI ids for laptops with bad Chassis Type */
1587e052067dSCorentin Chary 	{
1588e052067dSCorentin Chary 	  .ident = "R40/R41",
1589e052067dSCorentin Chary 	  .matches = {
1590e052067dSCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1591e052067dSCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1592e052067dSCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1593e052067dSCorentin Chary 		},
1594e052067dSCorentin Chary 	},
1595a979e2e2SCorentin Chary 	/* Specific DMI ids for laptop with quirks */
1596a979e2e2SCorentin Chary 	{
1597a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1598a979e2e2SCorentin Chary 	 .ident = "N150P",
1599a979e2e2SCorentin Chary 	 .matches = {
1600a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1601a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
1602a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N150P"),
1603a979e2e2SCorentin Chary 		},
16044690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1605a979e2e2SCorentin Chary 	},
1606a979e2e2SCorentin Chary 	{
1607a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1608a979e2e2SCorentin Chary 	 .ident = "N145P/N250P/N260P",
1609a979e2e2SCorentin Chary 	 .matches = {
1610a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1611a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1612a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1613a979e2e2SCorentin Chary 		},
16144690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1615a979e2e2SCorentin Chary 	},
1616a979e2e2SCorentin Chary 	{
1617a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1618a979e2e2SCorentin Chary 	 .ident = "N150/N210/N220",
1619a979e2e2SCorentin Chary 	 .matches = {
1620a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1621a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1622a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1623a979e2e2SCorentin Chary 		},
1624a979e2e2SCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
1625a979e2e2SCorentin Chary 	},
1626a979e2e2SCorentin Chary 	{
1627a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1628a979e2e2SCorentin Chary 	 .ident = "NF110/NF210/NF310",
1629a979e2e2SCorentin Chary 	 .matches = {
1630a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1631a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1632a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1633a979e2e2SCorentin Chary 		},
1634a979e2e2SCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
1635a979e2e2SCorentin Chary 	},
163609d5677cSCorentin Chary 	{
163709d5677cSCorentin Chary 	 .callback = samsung_dmi_matched,
163809d5677cSCorentin Chary 	 .ident = "X360",
163909d5677cSCorentin Chary 	 .matches = {
164009d5677cSCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
164109d5677cSCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
164209d5677cSCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "X360"),
164309d5677cSCorentin Chary 		},
164409d5677cSCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
164509d5677cSCorentin Chary 	},
1646e04c200fSSeth Forshee 	{
1647e04c200fSSeth Forshee 	 .callback = samsung_dmi_matched,
1648e04c200fSSeth Forshee 	 .ident = "N250P",
1649e04c200fSSeth Forshee 	 .matches = {
1650e04c200fSSeth Forshee 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1651e04c200fSSeth Forshee 		DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
1652e04c200fSSeth Forshee 		DMI_MATCH(DMI_BOARD_NAME, "N250P"),
1653e04c200fSSeth Forshee 		},
16544690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1655e04c200fSSeth Forshee 	},
16560ca849eaSScott Thrasher 	{
16570ca849eaSScott Thrasher 	 .callback = samsung_dmi_matched,
16585a1426c9SHans de Goede 	 .ident = "NC210",
16595a1426c9SHans de Goede 	 .matches = {
16605a1426c9SHans de Goede 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
16615a1426c9SHans de Goede 		DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
16625a1426c9SHans de Goede 		DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
16635a1426c9SHans de Goede 		},
16645a1426c9SHans de Goede 	 .driver_data = &samsung_broken_acpi_video,
16655a1426c9SHans de Goede 	},
16665a1426c9SHans de Goede 	{
16675a1426c9SHans de Goede 	 .callback = samsung_dmi_matched,
16680ca849eaSScott Thrasher 	 .ident = "730U3E/740U3E",
16690ca849eaSScott Thrasher 	 .matches = {
16700ca849eaSScott Thrasher 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
16710ca849eaSScott Thrasher 		DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
16720ca849eaSScott Thrasher 		},
16730ca849eaSScott Thrasher 	 .driver_data = &samsung_np740u3e,
16740ca849eaSScott Thrasher 	},
1675b0dcaf4fSJulijonas Kikutis 	{
1676b0dcaf4fSJulijonas Kikutis 	 .callback = samsung_dmi_matched,
1677b0dcaf4fSJulijonas Kikutis 	 .ident = "300V3Z/300V4Z/300V5Z",
1678b0dcaf4fSJulijonas Kikutis 	 .matches = {
1679b0dcaf4fSJulijonas Kikutis 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1680b0dcaf4fSJulijonas Kikutis 		DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
1681b0dcaf4fSJulijonas Kikutis 		},
1682b0dcaf4fSJulijonas Kikutis 	 .driver_data = &samsung_lid_handling,
1683b0dcaf4fSJulijonas Kikutis 	},
16842d70b73aSGreg Kroah-Hartman 	{ },
16852d70b73aSGreg Kroah-Hartman };
16862d70b73aSGreg Kroah-Hartman MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
16872d70b73aSGreg Kroah-Hartman 
16885dea7a20SCorentin Chary static struct platform_device *samsung_platform_device;
16892d70b73aSGreg Kroah-Hartman 
16902d70b73aSGreg Kroah-Hartman static int __init samsung_init(void)
16912d70b73aSGreg Kroah-Hartman {
16925dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
16935dea7a20SCorentin Chary 	int ret;
16942d70b73aSGreg Kroah-Hartman 
1695e0094244SMatt Fleming 	if (efi_enabled(EFI_BOOT))
1696e0094244SMatt Fleming 		return -ENODEV;
1697e0094244SMatt Fleming 
1698a979e2e2SCorentin Chary 	quirks = &samsung_unknown;
16992d70b73aSGreg Kroah-Hartman 	if (!force && !dmi_check_system(samsung_dmi_table))
17002d70b73aSGreg Kroah-Hartman 		return -ENODEV;
17012d70b73aSGreg Kroah-Hartman 
1702a6df4894SCorentin Chary 	samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1703a6df4894SCorentin Chary 	if (!samsung)
1704a6df4894SCorentin Chary 		return -ENOMEM;
1705a6df4894SCorentin Chary 
1706a6df4894SCorentin Chary 	mutex_init(&samsung->sabi_mutex);
1707f34cd9caSCorentin Chary 	samsung->handle_backlight = true;
1708a979e2e2SCorentin Chary 	samsung->quirks = quirks;
1709f34cd9caSCorentin Chary 
1710a60b2176SCorentin Chary #ifdef CONFIG_ACPI
1711a60b2176SCorentin Chary 	if (samsung->quirks->broken_acpi_video)
17126c072299SHans de Goede 		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
17136c072299SHans de Goede 	if (samsung->quirks->use_native_backlight)
17146c072299SHans de Goede 		acpi_video_set_dmi_backlight_type(acpi_backlight_native);
1715a60b2176SCorentin Chary 
17166c072299SHans de Goede 	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
1717f34cd9caSCorentin Chary 		samsung->handle_backlight = false;
1718f34cd9caSCorentin Chary #endif
1719a979e2e2SCorentin Chary 
17205dea7a20SCorentin Chary 	ret = samsung_platform_init(samsung);
17215dea7a20SCorentin Chary 	if (ret)
17225dea7a20SCorentin Chary 		goto error_platform;
17232d70b73aSGreg Kroah-Hartman 
17245dea7a20SCorentin Chary 	ret = samsung_sabi_init(samsung);
17255dea7a20SCorentin Chary 	if (ret)
17265dea7a20SCorentin Chary 		goto error_sabi;
17272d70b73aSGreg Kroah-Hartman 
17285dea7a20SCorentin Chary 	ret = samsung_sysfs_init(samsung);
17295dea7a20SCorentin Chary 	if (ret)
17305dea7a20SCorentin Chary 		goto error_sysfs;
17312d70b73aSGreg Kroah-Hartman 
17325dea7a20SCorentin Chary 	ret = samsung_backlight_init(samsung);
17335dea7a20SCorentin Chary 	if (ret)
17345dea7a20SCorentin Chary 		goto error_backlight;
1735a6df4894SCorentin Chary 
17365dea7a20SCorentin Chary 	ret = samsung_rfkill_init(samsung);
17375dea7a20SCorentin Chary 	if (ret)
17385dea7a20SCorentin Chary 		goto error_rfkill;
17392d70b73aSGreg Kroah-Hartman 
1740f674ebf1SCorentin Chary 	ret = samsung_leds_init(samsung);
1741f674ebf1SCorentin Chary 	if (ret)
1742f674ebf1SCorentin Chary 		goto error_leds;
1743f674ebf1SCorentin Chary 
1744b0dcaf4fSJulijonas Kikutis 	ret = samsung_lid_handling_init(samsung);
1745b0dcaf4fSJulijonas Kikutis 	if (ret)
1746b0dcaf4fSJulijonas Kikutis 		goto error_lid_handling;
1747b0dcaf4fSJulijonas Kikutis 
17485b80fc40SCorentin Chary 	ret = samsung_debugfs_init(samsung);
17495b80fc40SCorentin Chary 	if (ret)
17505b80fc40SCorentin Chary 		goto error_debugfs;
17515b80fc40SCorentin Chary 
17520ca849eaSScott Thrasher 	samsung->pm_nb.notifier_call = samsung_pm_notification;
17530ca849eaSScott Thrasher 	register_pm_notifier(&samsung->pm_nb);
17540ca849eaSScott Thrasher 
17555dea7a20SCorentin Chary 	samsung_platform_device = samsung->platform_device;
17565dea7a20SCorentin Chary 	return ret;
17572d70b73aSGreg Kroah-Hartman 
17585b80fc40SCorentin Chary error_debugfs:
1759b0dcaf4fSJulijonas Kikutis 	samsung_lid_handling_exit(samsung);
1760b0dcaf4fSJulijonas Kikutis error_lid_handling:
1761f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
1762f674ebf1SCorentin Chary error_leds:
17635b80fc40SCorentin Chary 	samsung_rfkill_exit(samsung);
17645dea7a20SCorentin Chary error_rfkill:
17655dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
17665dea7a20SCorentin Chary error_backlight:
17675dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
17685dea7a20SCorentin Chary error_sysfs:
17695dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
17705dea7a20SCorentin Chary error_sabi:
17715dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
17725dea7a20SCorentin Chary error_platform:
1773a6df4894SCorentin Chary 	kfree(samsung);
17745dea7a20SCorentin Chary 	return ret;
17752d70b73aSGreg Kroah-Hartman }
17762d70b73aSGreg Kroah-Hartman 
17772d70b73aSGreg Kroah-Hartman static void __exit samsung_exit(void)
17782d70b73aSGreg Kroah-Hartman {
17795dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
17802d70b73aSGreg Kroah-Hartman 
17815dea7a20SCorentin Chary 	samsung = platform_get_drvdata(samsung_platform_device);
17820ca849eaSScott Thrasher 	unregister_pm_notifier(&samsung->pm_nb);
1783a6df4894SCorentin Chary 
17845b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
1785b0dcaf4fSJulijonas Kikutis 	samsung_lid_handling_exit(samsung);
1786f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
17875dea7a20SCorentin Chary 	samsung_rfkill_exit(samsung);
17885dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
17895dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
17905dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
17915dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
17925dea7a20SCorentin Chary 
1793a6df4894SCorentin Chary 	kfree(samsung);
17945dea7a20SCorentin Chary 	samsung_platform_device = NULL;
17952d70b73aSGreg Kroah-Hartman }
17962d70b73aSGreg Kroah-Hartman 
17972d70b73aSGreg Kroah-Hartman module_init(samsung_init);
17982d70b73aSGreg Kroah-Hartman module_exit(samsung_exit);
17992d70b73aSGreg Kroah-Hartman 
18002d70b73aSGreg Kroah-Hartman MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
18012d70b73aSGreg Kroah-Hartman MODULE_DESCRIPTION("Samsung Backlight driver");
18022d70b73aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
1803