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