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 
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 	{
15584d482f2SCorentin Chary 		/* I don't know if it is really 2, but it 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 
197f674ebf1SCorentin Chary 			.kbd_backlight = 0xFFFF,
198f674ebf1SCorentin Chary 
1992d70b73aSGreg Kroah-Hartman 			.set_linux = 0x0a,
2002d70b73aSGreg Kroah-Hartman 		},
2012d70b73aSGreg Kroah-Hartman 
2022d70b73aSGreg Kroah-Hartman 		.performance_levels = {
2032d70b73aSGreg Kroah-Hartman 			{
2042d70b73aSGreg Kroah-Hartman 				.name = "silent",
2052d70b73aSGreg Kroah-Hartman 				.value = 0,
2062d70b73aSGreg Kroah-Hartman 			},
2072d70b73aSGreg Kroah-Hartman 			{
2082d70b73aSGreg Kroah-Hartman 				.name = "normal",
2092d70b73aSGreg Kroah-Hartman 				.value = 1,
2102d70b73aSGreg Kroah-Hartman 			},
2112d70b73aSGreg Kroah-Hartman 			{ },
2122d70b73aSGreg Kroah-Hartman 		},
2132d70b73aSGreg Kroah-Hartman 		.min_brightness = 1,
2142d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
2152d70b73aSGreg Kroah-Hartman 	},
2162d70b73aSGreg Kroah-Hartman 	{
21784d482f2SCorentin Chary 		.sabi_version = 3,
21884d482f2SCorentin Chary 
2192d70b73aSGreg Kroah-Hartman 		.test_string = "SwSmi@",
2202d70b73aSGreg Kroah-Hartman 
2212d70b73aSGreg Kroah-Hartman 		.main_function = 0x5843,
2222d70b73aSGreg Kroah-Hartman 
2232d70b73aSGreg Kroah-Hartman 		.header_offsets = {
2242d70b73aSGreg Kroah-Hartman 			.port = 0x00,
2252d70b73aSGreg Kroah-Hartman 			.re_mem = 0x04,
2262d70b73aSGreg Kroah-Hartman 			.iface_func = 0x02,
2272d70b73aSGreg Kroah-Hartman 			.en_mem = 0x03,
2282d70b73aSGreg Kroah-Hartman 			.data_offset = 0x05,
2292d70b73aSGreg Kroah-Hartman 			.data_segment = 0x07,
2302d70b73aSGreg Kroah-Hartman 		},
2312d70b73aSGreg Kroah-Hartman 
2322d70b73aSGreg Kroah-Hartman 		.commands = {
2332d70b73aSGreg Kroah-Hartman 			.get_brightness = 0x10,
2342d70b73aSGreg Kroah-Hartman 			.set_brightness = 0x11,
2352d70b73aSGreg Kroah-Hartman 
2362d70b73aSGreg Kroah-Hartman 			.get_wireless_button = 0x12,
2372d70b73aSGreg Kroah-Hartman 			.set_wireless_button = 0x13,
2382d70b73aSGreg Kroah-Hartman 
2392d70b73aSGreg Kroah-Hartman 			.get_backlight = 0x2d,
2402d70b73aSGreg Kroah-Hartman 			.set_backlight = 0x2e,
2412d70b73aSGreg Kroah-Hartman 
2422d70b73aSGreg Kroah-Hartman 			.get_recovery_mode = 0xff,
2432d70b73aSGreg Kroah-Hartman 			.set_recovery_mode = 0xff,
2442d70b73aSGreg Kroah-Hartman 
2452d70b73aSGreg Kroah-Hartman 			.get_performance_level = 0x31,
2462d70b73aSGreg Kroah-Hartman 			.set_performance_level = 0x32,
2472d70b73aSGreg Kroah-Hartman 
248cb5b5c91SCorentin Chary 			.get_battery_life_extender = 0x65,
249cb5b5c91SCorentin Chary 			.set_battery_life_extender = 0x66,
250cb5b5c91SCorentin Chary 
2513a75d378SCorentin Chary 			.get_usb_charge = 0x67,
2523a75d378SCorentin Chary 			.set_usb_charge = 0x68,
2533a75d378SCorentin Chary 
25484d482f2SCorentin Chary 			.get_wireless_status = 0x69,
25584d482f2SCorentin Chary 			.set_wireless_status = 0x6a,
25684d482f2SCorentin Chary 
257f674ebf1SCorentin Chary 			.kbd_backlight = 0x78,
258f674ebf1SCorentin Chary 
2592d70b73aSGreg Kroah-Hartman 			.set_linux = 0xff,
2602d70b73aSGreg Kroah-Hartman 		},
2612d70b73aSGreg Kroah-Hartman 
2622d70b73aSGreg Kroah-Hartman 		.performance_levels = {
2632d70b73aSGreg Kroah-Hartman 			{
2642d70b73aSGreg Kroah-Hartman 				.name = "normal",
2652d70b73aSGreg Kroah-Hartman 				.value = 0,
2662d70b73aSGreg Kroah-Hartman 			},
2672d70b73aSGreg Kroah-Hartman 			{
2682d70b73aSGreg Kroah-Hartman 				.name = "silent",
2692d70b73aSGreg Kroah-Hartman 				.value = 1,
2702d70b73aSGreg Kroah-Hartman 			},
2712d70b73aSGreg Kroah-Hartman 			{
2722d70b73aSGreg Kroah-Hartman 				.name = "overclock",
2732d70b73aSGreg Kroah-Hartman 				.value = 2,
2742d70b73aSGreg Kroah-Hartman 			},
2752d70b73aSGreg Kroah-Hartman 			{ },
2762d70b73aSGreg Kroah-Hartman 		},
2772d70b73aSGreg Kroah-Hartman 		.min_brightness = 0,
2782d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
2792d70b73aSGreg Kroah-Hartman 	},
2802d70b73aSGreg Kroah-Hartman 	{ },
2812d70b73aSGreg Kroah-Hartman };
2822d70b73aSGreg Kroah-Hartman 
2835b80fc40SCorentin Chary /*
2845b80fc40SCorentin Chary  * samsung-laptop/    - debugfs root directory
2855b80fc40SCorentin Chary  *   f0000_segment    - dump f0000 segment
2865b80fc40SCorentin Chary  *   command          - current command
2875b80fc40SCorentin Chary  *   data             - current data
2885b80fc40SCorentin Chary  *   d0, d1, d2, d3   - data fields
2895b80fc40SCorentin Chary  *   call             - call SABI using command and data
2905b80fc40SCorentin Chary  *
2915b80fc40SCorentin Chary  * This allow to call arbitrary sabi commands wihout
2925b80fc40SCorentin Chary  * modifying the driver at all.
2935b80fc40SCorentin Chary  * For example, setting the keyboard backlight brightness to 5
2945b80fc40SCorentin Chary  *
2955b80fc40SCorentin Chary  *  echo 0x78 > command
2965b80fc40SCorentin Chary  *  echo 0x0582 > d0
2975b80fc40SCorentin Chary  *  echo 0 > d1
2985b80fc40SCorentin Chary  *  echo 0 > d2
2995b80fc40SCorentin Chary  *  echo 0 > d3
3005b80fc40SCorentin Chary  *  cat call
3015b80fc40SCorentin Chary  */
3025b80fc40SCorentin Chary 
3035b80fc40SCorentin Chary struct samsung_laptop_debug {
3045b80fc40SCorentin Chary 	struct dentry *root;
3055b80fc40SCorentin Chary 	struct sabi_data data;
3065b80fc40SCorentin Chary 	u16 command;
3075b80fc40SCorentin Chary 
3085b80fc40SCorentin Chary 	struct debugfs_blob_wrapper f0000_wrapper;
3095b80fc40SCorentin Chary 	struct debugfs_blob_wrapper data_wrapper;
3106f6ae06eSCorentin Chary 	struct debugfs_blob_wrapper sdiag_wrapper;
3115b80fc40SCorentin Chary };
3125b80fc40SCorentin Chary 
31384d482f2SCorentin Chary struct samsung_laptop;
31484d482f2SCorentin Chary 
31584d482f2SCorentin Chary struct samsung_rfkill {
31684d482f2SCorentin Chary 	struct samsung_laptop *samsung;
31784d482f2SCorentin Chary 	struct rfkill *rfkill;
31884d482f2SCorentin Chary 	enum rfkill_type type;
31984d482f2SCorentin Chary };
32084d482f2SCorentin Chary 
321a6df4894SCorentin Chary struct samsung_laptop {
322a6df4894SCorentin Chary 	const struct sabi_config *config;
3232d70b73aSGreg Kroah-Hartman 
324a6df4894SCorentin Chary 	void __iomem *sabi;
325a6df4894SCorentin Chary 	void __iomem *sabi_iface;
326a6df4894SCorentin Chary 	void __iomem *f0000_segment;
327a6df4894SCorentin Chary 
328a6df4894SCorentin Chary 	struct mutex sabi_mutex;
329a6df4894SCorentin Chary 
3305dea7a20SCorentin Chary 	struct platform_device *platform_device;
331a6df4894SCorentin Chary 	struct backlight_device *backlight_device;
33284d482f2SCorentin Chary 
33384d482f2SCorentin Chary 	struct samsung_rfkill wlan;
33484d482f2SCorentin Chary 	struct samsung_rfkill bluetooth;
335a6df4894SCorentin Chary 
336f674ebf1SCorentin Chary 	struct led_classdev kbd_led;
337f674ebf1SCorentin Chary 	int kbd_led_wk;
338f674ebf1SCorentin Chary 	struct workqueue_struct *led_workqueue;
339f674ebf1SCorentin Chary 	struct work_struct kbd_led_work;
340f674ebf1SCorentin Chary 
3415b80fc40SCorentin Chary 	struct samsung_laptop_debug debug;
342a979e2e2SCorentin Chary 	struct samsung_quirks *quirks;
3435b80fc40SCorentin Chary 
3440ca849eaSScott Thrasher 	struct notifier_block pm_nb;
3450ca849eaSScott Thrasher 
346f34cd9caSCorentin Chary 	bool handle_backlight;
347a6df4894SCorentin Chary 	bool has_stepping_quirk;
3486f6ae06eSCorentin Chary 
3496f6ae06eSCorentin Chary 	char sdiag[64];
350a6df4894SCorentin Chary };
351a6df4894SCorentin Chary 
352a979e2e2SCorentin Chary struct samsung_quirks {
353a979e2e2SCorentin Chary 	bool broken_acpi_video;
3540ca849eaSScott Thrasher 	bool four_kbd_backlight_levels;
3550ca849eaSScott Thrasher 	bool enable_kbd_backlight;
3564690555eSHans de Goede 	bool use_native_backlight;
357a979e2e2SCorentin Chary };
3585dea7a20SCorentin Chary 
359a979e2e2SCorentin Chary static struct samsung_quirks samsung_unknown = {};
360a979e2e2SCorentin Chary 
361a979e2e2SCorentin Chary static struct samsung_quirks samsung_broken_acpi_video = {
362a979e2e2SCorentin Chary 	.broken_acpi_video = true,
363a979e2e2SCorentin Chary };
3642d70b73aSGreg Kroah-Hartman 
3654690555eSHans de Goede static struct samsung_quirks samsung_use_native_backlight = {
3664690555eSHans de Goede 	.use_native_backlight = true,
3674690555eSHans de Goede };
3684690555eSHans de Goede 
3690ca849eaSScott Thrasher static struct samsung_quirks samsung_np740u3e = {
3700ca849eaSScott Thrasher 	.four_kbd_backlight_levels = true,
3710ca849eaSScott Thrasher 	.enable_kbd_backlight = true,
3720ca849eaSScott Thrasher };
3730ca849eaSScott Thrasher 
37490ab5ee9SRusty Russell static bool force;
3752d70b73aSGreg Kroah-Hartman module_param(force, bool, 0);
3762d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(force,
3772d70b73aSGreg Kroah-Hartman 		"Disable the DMI check and forces the driver to be loaded");
3782d70b73aSGreg Kroah-Hartman 
37990ab5ee9SRusty Russell static bool debug;
3802d70b73aSGreg Kroah-Hartman module_param(debug, bool, S_IRUGO | S_IWUSR);
3812d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(debug, "Debug enabled or not");
3822d70b73aSGreg Kroah-Hartman 
3837e960711SCorentin Chary static int sabi_command(struct samsung_laptop *samsung, u16 command,
3847e960711SCorentin Chary 			struct sabi_data *in,
3857e960711SCorentin Chary 			struct sabi_data *out)
3862d70b73aSGreg Kroah-Hartman {
387a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
3887e960711SCorentin Chary 	int ret = 0;
389a6df4894SCorentin Chary 	u16 port = readw(samsung->sabi + config->header_offsets.port);
3902d70b73aSGreg Kroah-Hartman 	u8 complete, iface_data;
3912d70b73aSGreg Kroah-Hartman 
392a6df4894SCorentin Chary 	mutex_lock(&samsung->sabi_mutex);
3932d70b73aSGreg Kroah-Hartman 
3947e960711SCorentin Chary 	if (debug) {
3957e960711SCorentin Chary 		if (in)
3962e777187SCorentin Chary 			pr_info("SABI command:0x%04x "
3972e777187SCorentin Chary 				"data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
3987e960711SCorentin Chary 				command, in->d0, in->d1, in->d2, in->d3);
3997e960711SCorentin Chary 		else
4002e777187SCorentin Chary 			pr_info("SABI command:0x%04x", command);
4017e960711SCorentin Chary 	}
4027e960711SCorentin Chary 
4032d70b73aSGreg Kroah-Hartman 	/* enable memory to be able to write to it */
404a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
4052d70b73aSGreg Kroah-Hartman 
4062d70b73aSGreg Kroah-Hartman 	/* write out the command */
407a6df4894SCorentin Chary 	writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
408a6df4894SCorentin Chary 	writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
409a6df4894SCorentin Chary 	writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
4107e960711SCorentin Chary 	if (in) {
4117e960711SCorentin Chary 		writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
4127e960711SCorentin Chary 		writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
4137e960711SCorentin Chary 		writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
4147e960711SCorentin Chary 		writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
4157e960711SCorentin Chary 	}
416a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
4172d70b73aSGreg Kroah-Hartman 
4182d70b73aSGreg Kroah-Hartman 	/* write protect memory to make it safe */
419a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
4202d70b73aSGreg Kroah-Hartman 
4212d70b73aSGreg Kroah-Hartman 	/* see if the command actually succeeded */
422a6df4894SCorentin Chary 	complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
423a6df4894SCorentin Chary 	iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
4242e777187SCorentin Chary 
4252e777187SCorentin Chary 	/* iface_data = 0xFF happens when a command is not known
4262e777187SCorentin Chary 	 * so we only add a warning in debug mode since we will
4272e777187SCorentin Chary 	 * probably issue some unknown command at startup to find
4282e777187SCorentin Chary 	 * out which features are supported */
4292e777187SCorentin Chary 	if (complete != 0xaa || (iface_data == 0xff && debug))
4307e960711SCorentin Chary 		pr_warn("SABI command 0x%04x failed with"
4317e960711SCorentin Chary 			" completion flag 0x%02x and interface data 0x%02x",
4322d70b73aSGreg Kroah-Hartman 			command, complete, iface_data);
4332e777187SCorentin Chary 
4342e777187SCorentin Chary 	if (complete != 0xaa || iface_data == 0xff) {
4357e960711SCorentin Chary 		ret = -EINVAL;
4362d70b73aSGreg Kroah-Hartman 		goto exit;
4372d70b73aSGreg Kroah-Hartman 	}
4387e960711SCorentin Chary 
4397e960711SCorentin Chary 	if (out) {
4407e960711SCorentin Chary 		out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
4417e960711SCorentin Chary 		out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
4427e960711SCorentin Chary 		out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
4437e960711SCorentin Chary 		out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
4447e960711SCorentin Chary 	}
4457e960711SCorentin Chary 
4467e960711SCorentin Chary 	if (debug && out) {
4472e777187SCorentin Chary 		pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
4487e960711SCorentin Chary 			out->d0, out->d1, out->d2, out->d3);
4497e960711SCorentin Chary 	}
4502d70b73aSGreg Kroah-Hartman 
4512d70b73aSGreg Kroah-Hartman exit:
452a6df4894SCorentin Chary 	mutex_unlock(&samsung->sabi_mutex);
4537e960711SCorentin Chary 	return ret;
4542d70b73aSGreg Kroah-Hartman }
4552d70b73aSGreg Kroah-Hartman 
4567e960711SCorentin Chary /* simple wrappers usable with most commands */
4577e960711SCorentin Chary static int sabi_set_commandb(struct samsung_laptop *samsung,
4587e960711SCorentin Chary 			     u16 command, u8 data)
4592d70b73aSGreg Kroah-Hartman {
46085229440SDavid Rientjes 	struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
4612d70b73aSGreg Kroah-Hartman 
4627e960711SCorentin Chary 	in.data[0] = data;
4637e960711SCorentin Chary 	return sabi_command(samsung, command, &in, NULL);
4642d70b73aSGreg Kroah-Hartman }
4652d70b73aSGreg Kroah-Hartman 
4665dea7a20SCorentin Chary static int read_brightness(struct samsung_laptop *samsung)
4672d70b73aSGreg Kroah-Hartman {
468a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
469a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
4707e960711SCorentin Chary 	struct sabi_data sretval;
4712d70b73aSGreg Kroah-Hartman 	int user_brightness = 0;
4722d70b73aSGreg Kroah-Hartman 	int retval;
4732d70b73aSGreg Kroah-Hartman 
4747e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_brightness,
4757e960711SCorentin Chary 			      NULL, &sretval);
4767e960711SCorentin Chary 	if (retval)
4777e960711SCorentin Chary 		return retval;
4787e960711SCorentin Chary 
4797e960711SCorentin Chary 	user_brightness = sretval.data[0];
480a6df4894SCorentin Chary 	if (user_brightness > config->min_brightness)
481a6df4894SCorentin Chary 		user_brightness -= config->min_brightness;
482bee460beSJason Stubbs 	else
483bee460beSJason Stubbs 		user_brightness = 0;
4847e960711SCorentin Chary 
4852d70b73aSGreg Kroah-Hartman 	return user_brightness;
4862d70b73aSGreg Kroah-Hartman }
4872d70b73aSGreg Kroah-Hartman 
4885dea7a20SCorentin Chary static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
4892d70b73aSGreg Kroah-Hartman {
490a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
491a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
492a6df4894SCorentin Chary 	u8 user_level = user_brightness + config->min_brightness;
4932d70b73aSGreg Kroah-Hartman 
494a6df4894SCorentin Chary 	if (samsung->has_stepping_quirk && user_level != 0) {
495ac080523SJason Stubbs 		/*
496ac080523SJason Stubbs 		 * short circuit if the specified level is what's already set
497ac080523SJason Stubbs 		 * to prevent the screen from flickering needlessly
498ac080523SJason Stubbs 		 */
4995dea7a20SCorentin Chary 		if (user_brightness == read_brightness(samsung))
500ac080523SJason Stubbs 			return;
501ac080523SJason Stubbs 
5027e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_brightness, 0);
503ac080523SJason Stubbs 	}
504ac080523SJason Stubbs 
5057e960711SCorentin Chary 	sabi_set_commandb(samsung, commands->set_brightness, user_level);
5062d70b73aSGreg Kroah-Hartman }
5072d70b73aSGreg Kroah-Hartman 
5082d70b73aSGreg Kroah-Hartman static int get_brightness(struct backlight_device *bd)
5092d70b73aSGreg Kroah-Hartman {
5105dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
5115dea7a20SCorentin Chary 
5125dea7a20SCorentin Chary 	return read_brightness(samsung);
5132d70b73aSGreg Kroah-Hartman }
5142d70b73aSGreg Kroah-Hartman 
5155dea7a20SCorentin Chary static void check_for_stepping_quirk(struct samsung_laptop *samsung)
516ac080523SJason Stubbs {
5175dea7a20SCorentin Chary 	int initial_level;
5185dea7a20SCorentin Chary 	int check_level;
5195dea7a20SCorentin Chary 	int orig_level = read_brightness(samsung);
520ac080523SJason Stubbs 
521ac080523SJason Stubbs 	/*
522ac080523SJason Stubbs 	 * Some laptops exhibit the strange behaviour of stepping toward
523ac080523SJason Stubbs 	 * (rather than setting) the brightness except when changing to/from
524ac080523SJason Stubbs 	 * brightness level 0. This behaviour is checked for here and worked
525ac080523SJason Stubbs 	 * around in set_brightness.
526ac080523SJason Stubbs 	 */
527ac080523SJason Stubbs 
528ba05b237SJohn Serock 	if (orig_level == 0)
5295dea7a20SCorentin Chary 		set_brightness(samsung, 1);
530ba05b237SJohn Serock 
5315dea7a20SCorentin Chary 	initial_level = read_brightness(samsung);
532ba05b237SJohn Serock 
533ac080523SJason Stubbs 	if (initial_level <= 2)
534ac080523SJason Stubbs 		check_level = initial_level + 2;
535ac080523SJason Stubbs 	else
536ac080523SJason Stubbs 		check_level = initial_level - 2;
537ac080523SJason Stubbs 
538a6df4894SCorentin Chary 	samsung->has_stepping_quirk = false;
5395dea7a20SCorentin Chary 	set_brightness(samsung, check_level);
540ac080523SJason Stubbs 
5415dea7a20SCorentin Chary 	if (read_brightness(samsung) != check_level) {
542a6df4894SCorentin Chary 		samsung->has_stepping_quirk = true;
543ac080523SJason Stubbs 		pr_info("enabled workaround for brightness stepping quirk\n");
544ac080523SJason Stubbs 	}
545ac080523SJason Stubbs 
5465dea7a20SCorentin Chary 	set_brightness(samsung, orig_level);
547ac080523SJason Stubbs }
548ac080523SJason Stubbs 
5492d70b73aSGreg Kroah-Hartman static int update_status(struct backlight_device *bd)
5502d70b73aSGreg Kroah-Hartman {
5515dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
552a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
553a6df4894SCorentin Chary 
5545dea7a20SCorentin Chary 	set_brightness(samsung, bd->props.brightness);
5552d70b73aSGreg Kroah-Hartman 
5562d70b73aSGreg Kroah-Hartman 	if (bd->props.power == FB_BLANK_UNBLANK)
5577e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 1);
5582d70b73aSGreg Kroah-Hartman 	else
5597e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 0);
5605dea7a20SCorentin Chary 
5612d70b73aSGreg Kroah-Hartman 	return 0;
5622d70b73aSGreg Kroah-Hartman }
5632d70b73aSGreg Kroah-Hartman 
5642d70b73aSGreg Kroah-Hartman static const struct backlight_ops backlight_ops = {
5652d70b73aSGreg Kroah-Hartman 	.get_brightness	= get_brightness,
5662d70b73aSGreg Kroah-Hartman 	.update_status	= update_status,
5672d70b73aSGreg Kroah-Hartman };
5682d70b73aSGreg Kroah-Hartman 
56984d482f2SCorentin Chary static int seclinux_rfkill_set(void *data, bool blocked)
5702d70b73aSGreg Kroah-Hartman {
57120db88e3SCorentin Chary 	struct samsung_rfkill *srfkill = data;
57220db88e3SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
573a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
574a6df4894SCorentin Chary 
57584d482f2SCorentin Chary 	return sabi_set_commandb(samsung, commands->set_wireless_button,
57684d482f2SCorentin Chary 				 !blocked);
5772d70b73aSGreg Kroah-Hartman }
5782d70b73aSGreg Kroah-Hartman 
57984d482f2SCorentin Chary static struct rfkill_ops seclinux_rfkill_ops = {
58084d482f2SCorentin Chary 	.set_block = seclinux_rfkill_set,
58184d482f2SCorentin Chary };
58284d482f2SCorentin Chary 
58384d482f2SCorentin Chary static int swsmi_wireless_status(struct samsung_laptop *samsung,
58484d482f2SCorentin Chary 				 struct sabi_data *data)
58584d482f2SCorentin Chary {
58684d482f2SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
58784d482f2SCorentin Chary 
58884d482f2SCorentin Chary 	return sabi_command(samsung, commands->get_wireless_status,
58984d482f2SCorentin Chary 			    NULL, data);
59084d482f2SCorentin Chary }
59184d482f2SCorentin Chary 
59284d482f2SCorentin Chary static int swsmi_rfkill_set(void *priv, bool blocked)
59384d482f2SCorentin Chary {
59484d482f2SCorentin Chary 	struct samsung_rfkill *srfkill = priv;
59584d482f2SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
59684d482f2SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
59784d482f2SCorentin Chary 	struct sabi_data data;
59884d482f2SCorentin Chary 	int ret, i;
59984d482f2SCorentin Chary 
60084d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
60184d482f2SCorentin Chary 	if (ret)
60284d482f2SCorentin Chary 		return ret;
60384d482f2SCorentin Chary 
60484d482f2SCorentin Chary 	/* Don't set the state for non-present devices */
60584d482f2SCorentin Chary 	for (i = 0; i < 4; i++)
60684d482f2SCorentin Chary 		if (data.data[i] == 0x02)
60784d482f2SCorentin Chary 			data.data[1] = 0;
60884d482f2SCorentin Chary 
60984d482f2SCorentin Chary 	if (srfkill->type == RFKILL_TYPE_WLAN)
61084d482f2SCorentin Chary 		data.data[WL_STATUS_WLAN] = !blocked;
61184d482f2SCorentin Chary 	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
61284d482f2SCorentin Chary 		data.data[WL_STATUS_BT] = !blocked;
61384d482f2SCorentin Chary 
61484d482f2SCorentin Chary 	return sabi_command(samsung, commands->set_wireless_status,
61584d482f2SCorentin Chary 			    &data, &data);
61684d482f2SCorentin Chary }
61784d482f2SCorentin Chary 
61884d482f2SCorentin Chary static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
61984d482f2SCorentin Chary {
62084d482f2SCorentin Chary 	struct samsung_rfkill *srfkill = priv;
62184d482f2SCorentin Chary 	struct samsung_laptop *samsung = srfkill->samsung;
62284d482f2SCorentin Chary 	struct sabi_data data;
62384d482f2SCorentin Chary 	int ret;
62484d482f2SCorentin Chary 
62584d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
62684d482f2SCorentin Chary 	if (ret)
62784d482f2SCorentin Chary 		return ;
62884d482f2SCorentin Chary 
62984d482f2SCorentin Chary 	if (srfkill->type == RFKILL_TYPE_WLAN)
63084d482f2SCorentin Chary 		ret = data.data[WL_STATUS_WLAN];
63184d482f2SCorentin Chary 	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
63284d482f2SCorentin Chary 		ret = data.data[WL_STATUS_BT];
63384d482f2SCorentin Chary 	else
63484d482f2SCorentin Chary 		return ;
63584d482f2SCorentin Chary 
63684d482f2SCorentin Chary 	rfkill_set_sw_state(rfkill, !ret);
63784d482f2SCorentin Chary }
63884d482f2SCorentin Chary 
63984d482f2SCorentin Chary static struct rfkill_ops swsmi_rfkill_ops = {
64084d482f2SCorentin Chary 	.set_block = swsmi_rfkill_set,
64184d482f2SCorentin Chary 	.query = swsmi_rfkill_query,
6422d70b73aSGreg Kroah-Hartman };
6432d70b73aSGreg Kroah-Hartman 
6442d70b73aSGreg Kroah-Hartman static ssize_t get_performance_level(struct device *dev,
6452d70b73aSGreg Kroah-Hartman 				     struct device_attribute *attr, char *buf)
6462d70b73aSGreg Kroah-Hartman {
6475dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
648a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
6495dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
6507e960711SCorentin Chary 	struct sabi_data sretval;
6512d70b73aSGreg Kroah-Hartman 	int retval;
6522d70b73aSGreg Kroah-Hartman 	int i;
6532d70b73aSGreg Kroah-Hartman 
6542d70b73aSGreg Kroah-Hartman 	/* Read the state */
6557e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_performance_level,
6567e960711SCorentin Chary 			      NULL, &sretval);
6572d70b73aSGreg Kroah-Hartman 	if (retval)
6582d70b73aSGreg Kroah-Hartman 		return retval;
6592d70b73aSGreg Kroah-Hartman 
6602d70b73aSGreg Kroah-Hartman 	/* The logic is backwards, yeah, lots of fun... */
661a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
6627e960711SCorentin Chary 		if (sretval.data[0] == config->performance_levels[i].value)
663a6df4894SCorentin Chary 			return sprintf(buf, "%s\n", config->performance_levels[i].name);
6642d70b73aSGreg Kroah-Hartman 	}
6652d70b73aSGreg Kroah-Hartman 	return sprintf(buf, "%s\n", "unknown");
6662d70b73aSGreg Kroah-Hartman }
6672d70b73aSGreg Kroah-Hartman 
6682d70b73aSGreg Kroah-Hartman static ssize_t set_performance_level(struct device *dev,
6692d70b73aSGreg Kroah-Hartman 				struct device_attribute *attr, const char *buf,
6702d70b73aSGreg Kroah-Hartman 				size_t count)
6712d70b73aSGreg Kroah-Hartman {
6725dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
673a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
6745dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
6752d70b73aSGreg Kroah-Hartman 	int i;
6765dea7a20SCorentin Chary 
6775dea7a20SCorentin Chary 	if (count < 1)
6785dea7a20SCorentin Chary 		return count;
6795dea7a20SCorentin Chary 
680a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
6812d70b73aSGreg Kroah-Hartman 		const struct sabi_performance_level *level =
682a6df4894SCorentin Chary 			&config->performance_levels[i];
6832d70b73aSGreg Kroah-Hartman 		if (!strncasecmp(level->name, buf, strlen(level->name))) {
6847e960711SCorentin Chary 			sabi_set_commandb(samsung,
6855dea7a20SCorentin Chary 					  commands->set_performance_level,
6862d70b73aSGreg Kroah-Hartman 					  level->value);
6872d70b73aSGreg Kroah-Hartman 			break;
6882d70b73aSGreg Kroah-Hartman 		}
6892d70b73aSGreg Kroah-Hartman 	}
6905dea7a20SCorentin Chary 
691a6df4894SCorentin Chary 	if (!config->performance_levels[i].name)
6922d70b73aSGreg Kroah-Hartman 		return -EINVAL;
6935dea7a20SCorentin Chary 
6942d70b73aSGreg Kroah-Hartman 	return count;
6952d70b73aSGreg Kroah-Hartman }
6965dea7a20SCorentin Chary 
6972d70b73aSGreg Kroah-Hartman static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
6982d70b73aSGreg Kroah-Hartman 		   get_performance_level, set_performance_level);
6992d70b73aSGreg Kroah-Hartman 
700cb5b5c91SCorentin Chary static int read_battery_life_extender(struct samsung_laptop *samsung)
701cb5b5c91SCorentin Chary {
702cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
703cb5b5c91SCorentin Chary 	struct sabi_data data;
704cb5b5c91SCorentin Chary 	int retval;
705cb5b5c91SCorentin Chary 
706cb5b5c91SCorentin Chary 	if (commands->get_battery_life_extender == 0xFFFF)
707cb5b5c91SCorentin Chary 		return -ENODEV;
708cb5b5c91SCorentin Chary 
709cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
710cb5b5c91SCorentin Chary 	data.data[0] = 0x80;
711cb5b5c91SCorentin Chary 	retval = sabi_command(samsung, commands->get_battery_life_extender,
712cb5b5c91SCorentin Chary 			      &data, &data);
713cb5b5c91SCorentin Chary 
714cb5b5c91SCorentin Chary 	if (retval)
715cb5b5c91SCorentin Chary 		return retval;
716cb5b5c91SCorentin Chary 
717cb5b5c91SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
718cb5b5c91SCorentin Chary 		return -ENODEV;
719cb5b5c91SCorentin Chary 
720cb5b5c91SCorentin Chary 	return data.data[0];
721cb5b5c91SCorentin Chary }
722cb5b5c91SCorentin Chary 
723cb5b5c91SCorentin Chary static int write_battery_life_extender(struct samsung_laptop *samsung,
724cb5b5c91SCorentin Chary 				       int enabled)
725cb5b5c91SCorentin Chary {
726cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
727cb5b5c91SCorentin Chary 	struct sabi_data data;
728cb5b5c91SCorentin Chary 
729cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
730cb5b5c91SCorentin Chary 	data.data[0] = 0x80 | enabled;
731cb5b5c91SCorentin Chary 	return sabi_command(samsung, commands->set_battery_life_extender,
732cb5b5c91SCorentin Chary 			    &data, NULL);
733cb5b5c91SCorentin Chary }
734cb5b5c91SCorentin Chary 
735cb5b5c91SCorentin Chary static ssize_t get_battery_life_extender(struct device *dev,
736cb5b5c91SCorentin Chary 					 struct device_attribute *attr,
737cb5b5c91SCorentin Chary 					 char *buf)
738cb5b5c91SCorentin Chary {
739cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
740cb5b5c91SCorentin Chary 	int ret;
741cb5b5c91SCorentin Chary 
742cb5b5c91SCorentin Chary 	ret = read_battery_life_extender(samsung);
743cb5b5c91SCorentin Chary 	if (ret < 0)
744cb5b5c91SCorentin Chary 		return ret;
745cb5b5c91SCorentin Chary 
746cb5b5c91SCorentin Chary 	return sprintf(buf, "%d\n", ret);
747cb5b5c91SCorentin Chary }
748cb5b5c91SCorentin Chary 
749cb5b5c91SCorentin Chary static ssize_t set_battery_life_extender(struct device *dev,
750cb5b5c91SCorentin Chary 					struct device_attribute *attr,
751cb5b5c91SCorentin Chary 					const char *buf, size_t count)
752cb5b5c91SCorentin Chary {
753cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
754cb5b5c91SCorentin Chary 	int ret, value;
755cb5b5c91SCorentin Chary 
756cb5b5c91SCorentin Chary 	if (!count || sscanf(buf, "%i", &value) != 1)
757cb5b5c91SCorentin Chary 		return -EINVAL;
758cb5b5c91SCorentin Chary 
759cb5b5c91SCorentin Chary 	ret = write_battery_life_extender(samsung, !!value);
760cb5b5c91SCorentin Chary 	if (ret < 0)
761cb5b5c91SCorentin Chary 		return ret;
762cb5b5c91SCorentin Chary 
763cb5b5c91SCorentin Chary 	return count;
764cb5b5c91SCorentin Chary }
765cb5b5c91SCorentin Chary 
766cb5b5c91SCorentin Chary static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
767cb5b5c91SCorentin Chary 		   get_battery_life_extender, set_battery_life_extender);
768cb5b5c91SCorentin Chary 
7693a75d378SCorentin Chary static int read_usb_charge(struct samsung_laptop *samsung)
7703a75d378SCorentin Chary {
7713a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
7723a75d378SCorentin Chary 	struct sabi_data data;
7733a75d378SCorentin Chary 	int retval;
7743a75d378SCorentin Chary 
7753a75d378SCorentin Chary 	if (commands->get_usb_charge == 0xFFFF)
7763a75d378SCorentin Chary 		return -ENODEV;
7773a75d378SCorentin Chary 
7783a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
7793a75d378SCorentin Chary 	data.data[0] = 0x80;
7803a75d378SCorentin Chary 	retval = sabi_command(samsung, commands->get_usb_charge,
7813a75d378SCorentin Chary 			      &data, &data);
7823a75d378SCorentin Chary 
7833a75d378SCorentin Chary 	if (retval)
7843a75d378SCorentin Chary 		return retval;
7853a75d378SCorentin Chary 
7863a75d378SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
7873a75d378SCorentin Chary 		return -ENODEV;
7883a75d378SCorentin Chary 
7893a75d378SCorentin Chary 	return data.data[0];
7903a75d378SCorentin Chary }
7913a75d378SCorentin Chary 
7923a75d378SCorentin Chary static int write_usb_charge(struct samsung_laptop *samsung,
7933a75d378SCorentin Chary 			    int enabled)
7943a75d378SCorentin Chary {
7953a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
7963a75d378SCorentin Chary 	struct sabi_data data;
7973a75d378SCorentin Chary 
7983a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
7993a75d378SCorentin Chary 	data.data[0] = 0x80 | enabled;
8003a75d378SCorentin Chary 	return sabi_command(samsung, commands->set_usb_charge,
8013a75d378SCorentin Chary 			    &data, NULL);
8023a75d378SCorentin Chary }
8033a75d378SCorentin Chary 
8043a75d378SCorentin Chary static ssize_t get_usb_charge(struct device *dev,
8053a75d378SCorentin Chary 			      struct device_attribute *attr,
8063a75d378SCorentin Chary 			      char *buf)
8073a75d378SCorentin Chary {
8083a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
8093a75d378SCorentin Chary 	int ret;
8103a75d378SCorentin Chary 
8113a75d378SCorentin Chary 	ret = read_usb_charge(samsung);
8123a75d378SCorentin Chary 	if (ret < 0)
8133a75d378SCorentin Chary 		return ret;
8143a75d378SCorentin Chary 
8153a75d378SCorentin Chary 	return sprintf(buf, "%d\n", ret);
8163a75d378SCorentin Chary }
8173a75d378SCorentin Chary 
8183a75d378SCorentin Chary static ssize_t set_usb_charge(struct device *dev,
8193a75d378SCorentin Chary 			      struct device_attribute *attr,
8203a75d378SCorentin Chary 			      const char *buf, size_t count)
8213a75d378SCorentin Chary {
8223a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
8233a75d378SCorentin Chary 	int ret, value;
8243a75d378SCorentin Chary 
8253a75d378SCorentin Chary 	if (!count || sscanf(buf, "%i", &value) != 1)
8263a75d378SCorentin Chary 		return -EINVAL;
8273a75d378SCorentin Chary 
8283a75d378SCorentin Chary 	ret = write_usb_charge(samsung, !!value);
8293a75d378SCorentin Chary 	if (ret < 0)
8303a75d378SCorentin Chary 		return ret;
8313a75d378SCorentin Chary 
8323a75d378SCorentin Chary 	return count;
8333a75d378SCorentin Chary }
8343a75d378SCorentin Chary 
8353a75d378SCorentin Chary static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
8363a75d378SCorentin Chary 		   get_usb_charge, set_usb_charge);
8373a75d378SCorentin Chary 
838a66c1662SCorentin Chary static struct attribute *platform_attributes[] = {
839a66c1662SCorentin Chary 	&dev_attr_performance_level.attr,
840cb5b5c91SCorentin Chary 	&dev_attr_battery_life_extender.attr,
8413a75d378SCorentin Chary 	&dev_attr_usb_charge.attr,
842a66c1662SCorentin Chary 	NULL
843a66c1662SCorentin Chary };
8442d70b73aSGreg Kroah-Hartman 
8455dea7a20SCorentin Chary static int find_signature(void __iomem *memcheck, const char *testStr)
8465dea7a20SCorentin Chary {
8475dea7a20SCorentin Chary 	int i = 0;
8485dea7a20SCorentin Chary 	int loca;
8495dea7a20SCorentin Chary 
8505dea7a20SCorentin Chary 	for (loca = 0; loca < 0xffff; loca++) {
8515dea7a20SCorentin Chary 		char temp = readb(memcheck + loca);
8525dea7a20SCorentin Chary 
8535dea7a20SCorentin Chary 		if (temp == testStr[i]) {
8545dea7a20SCorentin Chary 			if (i == strlen(testStr)-1)
8555dea7a20SCorentin Chary 				break;
8565dea7a20SCorentin Chary 			++i;
8575dea7a20SCorentin Chary 		} else {
8585dea7a20SCorentin Chary 			i = 0;
8595dea7a20SCorentin Chary 		}
8605dea7a20SCorentin Chary 	}
8615dea7a20SCorentin Chary 	return loca;
8625dea7a20SCorentin Chary }
8635dea7a20SCorentin Chary 
8645dea7a20SCorentin Chary static void samsung_rfkill_exit(struct samsung_laptop *samsung)
8655dea7a20SCorentin Chary {
86684d482f2SCorentin Chary 	if (samsung->wlan.rfkill) {
86784d482f2SCorentin Chary 		rfkill_unregister(samsung->wlan.rfkill);
86884d482f2SCorentin Chary 		rfkill_destroy(samsung->wlan.rfkill);
86984d482f2SCorentin Chary 		samsung->wlan.rfkill = NULL;
8705dea7a20SCorentin Chary 	}
87184d482f2SCorentin Chary 	if (samsung->bluetooth.rfkill) {
87284d482f2SCorentin Chary 		rfkill_unregister(samsung->bluetooth.rfkill);
87384d482f2SCorentin Chary 		rfkill_destroy(samsung->bluetooth.rfkill);
87484d482f2SCorentin Chary 		samsung->bluetooth.rfkill = NULL;
87584d482f2SCorentin Chary 	}
87684d482f2SCorentin Chary }
87784d482f2SCorentin Chary 
87884d482f2SCorentin Chary static int samsung_new_rfkill(struct samsung_laptop *samsung,
87984d482f2SCorentin Chary 			      struct samsung_rfkill *arfkill,
88084d482f2SCorentin Chary 			      const char *name, enum rfkill_type type,
88184d482f2SCorentin Chary 			      const struct rfkill_ops *ops,
88284d482f2SCorentin Chary 			      int blocked)
88384d482f2SCorentin Chary {
88484d482f2SCorentin Chary 	struct rfkill **rfkill = &arfkill->rfkill;
88584d482f2SCorentin Chary 	int ret;
88684d482f2SCorentin Chary 
88784d482f2SCorentin Chary 	arfkill->type = type;
88884d482f2SCorentin Chary 	arfkill->samsung = samsung;
88984d482f2SCorentin Chary 
89084d482f2SCorentin Chary 	*rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
89184d482f2SCorentin Chary 			       type, ops, arfkill);
89284d482f2SCorentin Chary 
89384d482f2SCorentin Chary 	if (!*rfkill)
89484d482f2SCorentin Chary 		return -EINVAL;
89584d482f2SCorentin Chary 
89684d482f2SCorentin Chary 	if (blocked != -1)
89784d482f2SCorentin Chary 		rfkill_init_sw_state(*rfkill, blocked);
89884d482f2SCorentin Chary 
89984d482f2SCorentin Chary 	ret = rfkill_register(*rfkill);
90084d482f2SCorentin Chary 	if (ret) {
90184d482f2SCorentin Chary 		rfkill_destroy(*rfkill);
90284d482f2SCorentin Chary 		*rfkill = NULL;
90384d482f2SCorentin Chary 		return ret;
90484d482f2SCorentin Chary 	}
90584d482f2SCorentin Chary 	return 0;
90684d482f2SCorentin Chary }
90784d482f2SCorentin Chary 
90884d482f2SCorentin Chary static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
90984d482f2SCorentin Chary {
91084d482f2SCorentin Chary 	return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
91184d482f2SCorentin Chary 				  RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
91284d482f2SCorentin Chary }
91384d482f2SCorentin Chary 
91484d482f2SCorentin Chary static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
91584d482f2SCorentin Chary {
91684d482f2SCorentin Chary 	struct sabi_data data;
91784d482f2SCorentin Chary 	int ret;
91884d482f2SCorentin Chary 
91984d482f2SCorentin Chary 	ret = swsmi_wireless_status(samsung, &data);
92020db88e3SCorentin Chary 	if (ret) {
92120db88e3SCorentin Chary 		/* Some swsmi laptops use the old seclinux way to control
92220db88e3SCorentin Chary 		 * wireless devices */
92320db88e3SCorentin Chary 		if (ret == -EINVAL)
92420db88e3SCorentin Chary 			ret = samsung_rfkill_init_seclinux(samsung);
92584d482f2SCorentin Chary 		return ret;
92620db88e3SCorentin Chary 	}
92784d482f2SCorentin Chary 
92884d482f2SCorentin Chary 	/* 0x02 seems to mean that the device is no present/available */
92984d482f2SCorentin Chary 
93084d482f2SCorentin Chary 	if (data.data[WL_STATUS_WLAN] != 0x02)
93184d482f2SCorentin Chary 		ret = samsung_new_rfkill(samsung, &samsung->wlan,
93284d482f2SCorentin Chary 					 "samsung-wlan",
93384d482f2SCorentin Chary 					 RFKILL_TYPE_WLAN,
93484d482f2SCorentin Chary 					 &swsmi_rfkill_ops,
93584d482f2SCorentin Chary 					 !data.data[WL_STATUS_WLAN]);
93684d482f2SCorentin Chary 	if (ret)
93784d482f2SCorentin Chary 		goto exit;
93884d482f2SCorentin Chary 
93984d482f2SCorentin Chary 	if (data.data[WL_STATUS_BT] != 0x02)
94084d482f2SCorentin Chary 		ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
94184d482f2SCorentin Chary 					 "samsung-bluetooth",
94284d482f2SCorentin Chary 					 RFKILL_TYPE_BLUETOOTH,
94384d482f2SCorentin Chary 					 &swsmi_rfkill_ops,
94484d482f2SCorentin Chary 					 !data.data[WL_STATUS_BT]);
94584d482f2SCorentin Chary 	if (ret)
94684d482f2SCorentin Chary 		goto exit;
94784d482f2SCorentin Chary 
94884d482f2SCorentin Chary exit:
94984d482f2SCorentin Chary 	if (ret)
95084d482f2SCorentin Chary 		samsung_rfkill_exit(samsung);
95184d482f2SCorentin Chary 
95284d482f2SCorentin Chary 	return ret;
9535dea7a20SCorentin Chary }
9545dea7a20SCorentin Chary 
9555dea7a20SCorentin Chary static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
9565dea7a20SCorentin Chary {
95784d482f2SCorentin Chary 	if (samsung->config->sabi_version == 2)
95884d482f2SCorentin Chary 		return samsung_rfkill_init_seclinux(samsung);
95984d482f2SCorentin Chary 	if (samsung->config->sabi_version == 3)
96084d482f2SCorentin Chary 		return samsung_rfkill_init_swsmi(samsung);
9615dea7a20SCorentin Chary 	return 0;
9625dea7a20SCorentin Chary }
9635dea7a20SCorentin Chary 
964f674ebf1SCorentin Chary static int kbd_backlight_enable(struct samsung_laptop *samsung)
965f674ebf1SCorentin Chary {
966f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
967f674ebf1SCorentin Chary 	struct sabi_data data;
968f674ebf1SCorentin Chary 	int retval;
969f674ebf1SCorentin Chary 
970f674ebf1SCorentin Chary 	if (commands->kbd_backlight == 0xFFFF)
971f674ebf1SCorentin Chary 		return -ENODEV;
972f674ebf1SCorentin Chary 
973f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
974f674ebf1SCorentin Chary 	data.d0 = 0xaabb;
975f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
976f674ebf1SCorentin Chary 			      &data, &data);
977f674ebf1SCorentin Chary 
978f674ebf1SCorentin Chary 	if (retval)
979f674ebf1SCorentin Chary 		return retval;
980f674ebf1SCorentin Chary 
981f674ebf1SCorentin Chary 	if (data.d0 != 0xccdd)
982f674ebf1SCorentin Chary 		return -ENODEV;
983f674ebf1SCorentin Chary 	return 0;
984f674ebf1SCorentin Chary }
985f674ebf1SCorentin Chary 
986f674ebf1SCorentin Chary static int kbd_backlight_read(struct samsung_laptop *samsung)
987f674ebf1SCorentin Chary {
988f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
989f674ebf1SCorentin Chary 	struct sabi_data data;
990f674ebf1SCorentin Chary 	int retval;
991f674ebf1SCorentin Chary 
992f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
993f674ebf1SCorentin Chary 	data.data[0] = 0x81;
994f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
995f674ebf1SCorentin Chary 			      &data, &data);
996f674ebf1SCorentin Chary 
997f674ebf1SCorentin Chary 	if (retval)
998f674ebf1SCorentin Chary 		return retval;
999f674ebf1SCorentin Chary 
1000f674ebf1SCorentin Chary 	return data.data[0];
1001f674ebf1SCorentin Chary }
1002f674ebf1SCorentin Chary 
1003f674ebf1SCorentin Chary static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1004f674ebf1SCorentin Chary {
1005f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
1006f674ebf1SCorentin Chary 	struct sabi_data data;
1007f674ebf1SCorentin Chary 
1008f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
1009f674ebf1SCorentin Chary 	data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1010f674ebf1SCorentin Chary 	return sabi_command(samsung, commands->kbd_backlight,
1011f674ebf1SCorentin Chary 			    &data, NULL);
1012f674ebf1SCorentin Chary }
1013f674ebf1SCorentin Chary 
1014f674ebf1SCorentin Chary static void kbd_led_update(struct work_struct *work)
1015f674ebf1SCorentin Chary {
1016f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1017f674ebf1SCorentin Chary 
1018f674ebf1SCorentin Chary 	samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1019f674ebf1SCorentin Chary 	kbd_backlight_write(samsung, samsung->kbd_led_wk);
1020f674ebf1SCorentin Chary }
1021f674ebf1SCorentin Chary 
1022f674ebf1SCorentin Chary static void kbd_led_set(struct led_classdev *led_cdev,
1023f674ebf1SCorentin Chary 			enum led_brightness value)
1024f674ebf1SCorentin Chary {
1025f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1026f674ebf1SCorentin Chary 
1027f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1028f674ebf1SCorentin Chary 
1029f674ebf1SCorentin Chary 	if (value > samsung->kbd_led.max_brightness)
1030f674ebf1SCorentin Chary 		value = samsung->kbd_led.max_brightness;
1031f674ebf1SCorentin Chary 	else if (value < 0)
1032f674ebf1SCorentin Chary 		value = 0;
1033f674ebf1SCorentin Chary 
1034f674ebf1SCorentin Chary 	samsung->kbd_led_wk = value;
1035f674ebf1SCorentin Chary 	queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1036f674ebf1SCorentin Chary }
1037f674ebf1SCorentin Chary 
1038f674ebf1SCorentin Chary static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1039f674ebf1SCorentin Chary {
1040f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
1041f674ebf1SCorentin Chary 
1042f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1043f674ebf1SCorentin Chary 	return kbd_backlight_read(samsung);
1044f674ebf1SCorentin Chary }
1045f674ebf1SCorentin Chary 
1046f674ebf1SCorentin Chary static void samsung_leds_exit(struct samsung_laptop *samsung)
1047f674ebf1SCorentin Chary {
1048f674ebf1SCorentin Chary 	if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
1049f674ebf1SCorentin Chary 		led_classdev_unregister(&samsung->kbd_led);
1050f674ebf1SCorentin Chary 	if (samsung->led_workqueue)
1051f674ebf1SCorentin Chary 		destroy_workqueue(samsung->led_workqueue);
1052f674ebf1SCorentin Chary }
1053f674ebf1SCorentin Chary 
1054f674ebf1SCorentin Chary static int __init samsung_leds_init(struct samsung_laptop *samsung)
1055f674ebf1SCorentin Chary {
1056f674ebf1SCorentin Chary 	int ret = 0;
1057f674ebf1SCorentin Chary 
1058f674ebf1SCorentin Chary 	samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1059f674ebf1SCorentin Chary 	if (!samsung->led_workqueue)
1060f674ebf1SCorentin Chary 		return -ENOMEM;
1061f674ebf1SCorentin Chary 
1062f674ebf1SCorentin Chary 	if (kbd_backlight_enable(samsung) >= 0) {
1063f674ebf1SCorentin Chary 		INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1064f674ebf1SCorentin Chary 
1065f674ebf1SCorentin Chary 		samsung->kbd_led.name = "samsung::kbd_backlight";
1066f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_set = kbd_led_set;
1067f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_get = kbd_led_get;
1068f674ebf1SCorentin Chary 		samsung->kbd_led.max_brightness = 8;
10690ca849eaSScott Thrasher 		if (samsung->quirks->four_kbd_backlight_levels)
10700ca849eaSScott Thrasher 			samsung->kbd_led.max_brightness = 4;
1071f674ebf1SCorentin Chary 
1072f674ebf1SCorentin Chary 		ret = led_classdev_register(&samsung->platform_device->dev,
1073f674ebf1SCorentin Chary 					   &samsung->kbd_led);
1074f674ebf1SCorentin Chary 	}
1075f674ebf1SCorentin Chary 
1076f674ebf1SCorentin Chary 	if (ret)
1077f674ebf1SCorentin Chary 		samsung_leds_exit(samsung);
1078f674ebf1SCorentin Chary 
1079f674ebf1SCorentin Chary 	return ret;
1080f674ebf1SCorentin Chary }
1081f674ebf1SCorentin Chary 
10825dea7a20SCorentin Chary static void samsung_backlight_exit(struct samsung_laptop *samsung)
10835dea7a20SCorentin Chary {
10845dea7a20SCorentin Chary 	if (samsung->backlight_device) {
10855dea7a20SCorentin Chary 		backlight_device_unregister(samsung->backlight_device);
10865dea7a20SCorentin Chary 		samsung->backlight_device = NULL;
10875dea7a20SCorentin Chary 	}
10885dea7a20SCorentin Chary }
10895dea7a20SCorentin Chary 
10905dea7a20SCorentin Chary static int __init samsung_backlight_init(struct samsung_laptop *samsung)
10915dea7a20SCorentin Chary {
10925dea7a20SCorentin Chary 	struct backlight_device *bd;
10935dea7a20SCorentin Chary 	struct backlight_properties props;
10945dea7a20SCorentin Chary 
1095f34cd9caSCorentin Chary 	if (!samsung->handle_backlight)
1096f34cd9caSCorentin Chary 		return 0;
1097f34cd9caSCorentin Chary 
10985dea7a20SCorentin Chary 	memset(&props, 0, sizeof(struct backlight_properties));
10995dea7a20SCorentin Chary 	props.type = BACKLIGHT_PLATFORM;
11005dea7a20SCorentin Chary 	props.max_brightness = samsung->config->max_brightness -
11015dea7a20SCorentin Chary 		samsung->config->min_brightness;
11025dea7a20SCorentin Chary 
11035dea7a20SCorentin Chary 	bd = backlight_device_register("samsung",
11045dea7a20SCorentin Chary 				       &samsung->platform_device->dev,
11055dea7a20SCorentin Chary 				       samsung, &backlight_ops,
11065dea7a20SCorentin Chary 				       &props);
11075dea7a20SCorentin Chary 	if (IS_ERR(bd))
11085dea7a20SCorentin Chary 		return PTR_ERR(bd);
11095dea7a20SCorentin Chary 
11105dea7a20SCorentin Chary 	samsung->backlight_device = bd;
11115dea7a20SCorentin Chary 	samsung->backlight_device->props.brightness = read_brightness(samsung);
11125dea7a20SCorentin Chary 	samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
11135dea7a20SCorentin Chary 	backlight_update_status(samsung->backlight_device);
11145dea7a20SCorentin Chary 
11155dea7a20SCorentin Chary 	return 0;
11165dea7a20SCorentin Chary }
11175dea7a20SCorentin Chary 
1118bde9e509SDan Carpenter static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1119a66c1662SCorentin Chary 				       struct attribute *attr, int idx)
1120a66c1662SCorentin Chary {
1121a66c1662SCorentin Chary 	struct device *dev = container_of(kobj, struct device, kobj);
1122a66c1662SCorentin Chary 	struct platform_device *pdev = to_platform_device(dev);
1123a66c1662SCorentin Chary 	struct samsung_laptop *samsung = platform_get_drvdata(pdev);
1124a66c1662SCorentin Chary 	bool ok = true;
1125a66c1662SCorentin Chary 
1126a66c1662SCorentin Chary 	if (attr == &dev_attr_performance_level.attr)
1127a66c1662SCorentin Chary 		ok = !!samsung->config->performance_levels[0].name;
1128cb5b5c91SCorentin Chary 	if (attr == &dev_attr_battery_life_extender.attr)
1129cb5b5c91SCorentin Chary 		ok = !!(read_battery_life_extender(samsung) >= 0);
11303a75d378SCorentin Chary 	if (attr == &dev_attr_usb_charge.attr)
11313a75d378SCorentin Chary 		ok = !!(read_usb_charge(samsung) >= 0);
1132a66c1662SCorentin Chary 
1133a66c1662SCorentin Chary 	return ok ? attr->mode : 0;
1134a66c1662SCorentin Chary }
1135a66c1662SCorentin Chary 
1136a66c1662SCorentin Chary static struct attribute_group platform_attribute_group = {
1137a66c1662SCorentin Chary 	.is_visible = samsung_sysfs_is_visible,
1138a66c1662SCorentin Chary 	.attrs = platform_attributes
1139a66c1662SCorentin Chary };
1140a66c1662SCorentin Chary 
11415dea7a20SCorentin Chary static void samsung_sysfs_exit(struct samsung_laptop *samsung)
11425dea7a20SCorentin Chary {
1143a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
1144a66c1662SCorentin Chary 
1145a66c1662SCorentin Chary 	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
11465dea7a20SCorentin Chary }
11475dea7a20SCorentin Chary 
11485dea7a20SCorentin Chary static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
11495dea7a20SCorentin Chary {
1150a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
1151a66c1662SCorentin Chary 
1152a66c1662SCorentin Chary 	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1153a66c1662SCorentin Chary 
11545dea7a20SCorentin Chary }
11555dea7a20SCorentin Chary 
11565b80fc40SCorentin Chary static int show_call(struct seq_file *m, void *data)
11575b80fc40SCorentin Chary {
11585b80fc40SCorentin Chary 	struct samsung_laptop *samsung = m->private;
11595b80fc40SCorentin Chary 	struct sabi_data *sdata = &samsung->debug.data;
11605b80fc40SCorentin Chary 	int ret;
11615b80fc40SCorentin Chary 
11625b80fc40SCorentin Chary 	seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
11635b80fc40SCorentin Chary 		   samsung->debug.command,
11645b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
11655b80fc40SCorentin Chary 
11665b80fc40SCorentin Chary 	ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
11675b80fc40SCorentin Chary 
11685b80fc40SCorentin Chary 	if (ret) {
11695b80fc40SCorentin Chary 		seq_printf(m, "SABI command 0x%04x failed\n",
11705b80fc40SCorentin Chary 			   samsung->debug.command);
11715b80fc40SCorentin Chary 		return ret;
11725b80fc40SCorentin Chary 	}
11735b80fc40SCorentin Chary 
11745b80fc40SCorentin Chary 	seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
11755b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
11765b80fc40SCorentin Chary 	return 0;
11775b80fc40SCorentin Chary }
11785b80fc40SCorentin Chary 
11795b80fc40SCorentin Chary static int samsung_debugfs_open(struct inode *inode, struct file *file)
11805b80fc40SCorentin Chary {
11815b80fc40SCorentin Chary 	return single_open(file, show_call, inode->i_private);
11825b80fc40SCorentin Chary }
11835b80fc40SCorentin Chary 
11845b80fc40SCorentin Chary static const struct file_operations samsung_laptop_call_io_ops = {
11855b80fc40SCorentin Chary 	.owner = THIS_MODULE,
11865b80fc40SCorentin Chary 	.open = samsung_debugfs_open,
11875b80fc40SCorentin Chary 	.read = seq_read,
11885b80fc40SCorentin Chary 	.llseek = seq_lseek,
11895b80fc40SCorentin Chary 	.release = single_release,
11905b80fc40SCorentin Chary };
11915b80fc40SCorentin Chary 
11925b80fc40SCorentin Chary static void samsung_debugfs_exit(struct samsung_laptop *samsung)
11935b80fc40SCorentin Chary {
11945b80fc40SCorentin Chary 	debugfs_remove_recursive(samsung->debug.root);
11955b80fc40SCorentin Chary }
11965b80fc40SCorentin Chary 
11975b80fc40SCorentin Chary static int samsung_debugfs_init(struct samsung_laptop *samsung)
11985b80fc40SCorentin Chary {
11995b80fc40SCorentin Chary 	struct dentry *dent;
12005b80fc40SCorentin Chary 
12015b80fc40SCorentin Chary 	samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
12025b80fc40SCorentin Chary 	if (!samsung->debug.root) {
12035b80fc40SCorentin Chary 		pr_err("failed to create debugfs directory");
12045b80fc40SCorentin Chary 		goto error_debugfs;
12055b80fc40SCorentin Chary 	}
12065b80fc40SCorentin Chary 
12075b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
12085b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.size = 0xffff;
12095b80fc40SCorentin Chary 
12105b80fc40SCorentin Chary 	samsung->debug.data_wrapper.data = &samsung->debug.data;
12115b80fc40SCorentin Chary 	samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
12125b80fc40SCorentin Chary 
12136f6ae06eSCorentin Chary 	samsung->debug.sdiag_wrapper.data = samsung->sdiag;
12146f6ae06eSCorentin Chary 	samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
12156f6ae06eSCorentin Chary 
12165b80fc40SCorentin Chary 	dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
12175b80fc40SCorentin Chary 				  samsung->debug.root, &samsung->debug.command);
12185b80fc40SCorentin Chary 	if (!dent)
12195b80fc40SCorentin Chary 		goto error_debugfs;
12205b80fc40SCorentin Chary 
12215b80fc40SCorentin Chary 	dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
12225b80fc40SCorentin Chary 				  &samsung->debug.data.d0);
12235b80fc40SCorentin Chary 	if (!dent)
12245b80fc40SCorentin Chary 		goto error_debugfs;
12255b80fc40SCorentin Chary 
12265b80fc40SCorentin Chary 	dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
12275b80fc40SCorentin Chary 				  &samsung->debug.data.d1);
12285b80fc40SCorentin Chary 	if (!dent)
12295b80fc40SCorentin Chary 		goto error_debugfs;
12305b80fc40SCorentin Chary 
12315b80fc40SCorentin Chary 	dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
12325b80fc40SCorentin Chary 				  &samsung->debug.data.d2);
12335b80fc40SCorentin Chary 	if (!dent)
12345b80fc40SCorentin Chary 		goto error_debugfs;
12355b80fc40SCorentin Chary 
12365b80fc40SCorentin Chary 	dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
12375b80fc40SCorentin Chary 				 &samsung->debug.data.d3);
12385b80fc40SCorentin Chary 	if (!dent)
12395b80fc40SCorentin Chary 		goto error_debugfs;
12405b80fc40SCorentin Chary 
12415b80fc40SCorentin Chary 	dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
12425b80fc40SCorentin Chary 				   samsung->debug.root,
12435b80fc40SCorentin Chary 				   &samsung->debug.data_wrapper);
12445b80fc40SCorentin Chary 	if (!dent)
12455b80fc40SCorentin Chary 		goto error_debugfs;
12465b80fc40SCorentin Chary 
12475b80fc40SCorentin Chary 	dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
12485b80fc40SCorentin Chary 				   samsung->debug.root,
12495b80fc40SCorentin Chary 				   &samsung->debug.f0000_wrapper);
12505b80fc40SCorentin Chary 	if (!dent)
12515b80fc40SCorentin Chary 		goto error_debugfs;
12525b80fc40SCorentin Chary 
12535b80fc40SCorentin Chary 	dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
12545b80fc40SCorentin Chary 				   samsung->debug.root, samsung,
12555b80fc40SCorentin Chary 				   &samsung_laptop_call_io_ops);
12565b80fc40SCorentin Chary 	if (!dent)
12575b80fc40SCorentin Chary 		goto error_debugfs;
12585b80fc40SCorentin Chary 
12596f6ae06eSCorentin Chary 	dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
12606f6ae06eSCorentin Chary 				   samsung->debug.root,
12616f6ae06eSCorentin Chary 				   &samsung->debug.sdiag_wrapper);
12626f6ae06eSCorentin Chary 	if (!dent)
12636f6ae06eSCorentin Chary 		goto error_debugfs;
12646f6ae06eSCorentin Chary 
12655b80fc40SCorentin Chary 	return 0;
12665b80fc40SCorentin Chary 
12675b80fc40SCorentin Chary error_debugfs:
12685b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
12695b80fc40SCorentin Chary 	return -ENOMEM;
12705b80fc40SCorentin Chary }
12715b80fc40SCorentin Chary 
12725dea7a20SCorentin Chary static void samsung_sabi_exit(struct samsung_laptop *samsung)
12735dea7a20SCorentin Chary {
12745dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
12755dea7a20SCorentin Chary 
12765dea7a20SCorentin Chary 	/* Turn off "Linux" mode in the BIOS */
12775dea7a20SCorentin Chary 	if (config && config->commands.set_linux != 0xff)
12787e960711SCorentin Chary 		sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
12795dea7a20SCorentin Chary 
12805dea7a20SCorentin Chary 	if (samsung->sabi_iface) {
12815dea7a20SCorentin Chary 		iounmap(samsung->sabi_iface);
12825dea7a20SCorentin Chary 		samsung->sabi_iface = NULL;
12835dea7a20SCorentin Chary 	}
12845dea7a20SCorentin Chary 	if (samsung->f0000_segment) {
12855dea7a20SCorentin Chary 		iounmap(samsung->f0000_segment);
12865dea7a20SCorentin Chary 		samsung->f0000_segment = NULL;
12875dea7a20SCorentin Chary 	}
12885dea7a20SCorentin Chary 
12895dea7a20SCorentin Chary 	samsung->config = NULL;
12905dea7a20SCorentin Chary }
12915dea7a20SCorentin Chary 
129249dd7730SCorentin Chary static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
129349dd7730SCorentin Chary 				      unsigned int ifaceP)
12945dea7a20SCorentin Chary {
12955dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
12965dea7a20SCorentin Chary 
12975dea7a20SCorentin Chary 	printk(KERN_DEBUG "This computer supports SABI==%x\n",
12985dea7a20SCorentin Chary 	       loca + 0xf0000 - 6);
129949dd7730SCorentin Chary 
13005dea7a20SCorentin Chary 	printk(KERN_DEBUG "SABI header:\n");
13015dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
13025dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.port));
13035dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
13045dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.iface_func));
13055dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
13065dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.en_mem));
13075dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
13085dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.re_mem));
13095dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
13105dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_offset));
13115dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
13125dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_segment));
13135dea7a20SCorentin Chary 
131449dd7730SCorentin Chary 	printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
13155dea7a20SCorentin Chary }
13165dea7a20SCorentin Chary 
13176f6ae06eSCorentin Chary static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
13186f6ae06eSCorentin Chary {
13196f6ae06eSCorentin Chary 	int loca = find_signature(samsung->f0000_segment, "SDiaG@");
13206f6ae06eSCorentin Chary 	int i;
13216f6ae06eSCorentin Chary 
13226f6ae06eSCorentin Chary 	if (loca == 0xffff)
13236f6ae06eSCorentin Chary 		return ;
13246f6ae06eSCorentin Chary 
13256f6ae06eSCorentin Chary 	/* Example:
13266f6ae06eSCorentin Chary 	 * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
13276f6ae06eSCorentin Chary 	 *
13286f6ae06eSCorentin Chary 	 * Product name: 90X3A
13296f6ae06eSCorentin Chary 	 * BIOS Version: 07HL
13306f6ae06eSCorentin Chary 	 */
13316f6ae06eSCorentin Chary 	loca += 1;
13326f6ae06eSCorentin Chary 	for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
13336f6ae06eSCorentin Chary 		char temp = readb(samsung->f0000_segment + loca);
13346f6ae06eSCorentin Chary 
13356f6ae06eSCorentin Chary 		if (isalnum(temp) || temp == '/' || temp == '-')
13366f6ae06eSCorentin Chary 			samsung->sdiag[i++] = temp;
13376f6ae06eSCorentin Chary 		else
13386f6ae06eSCorentin Chary 			break ;
13396f6ae06eSCorentin Chary 	}
13406f6ae06eSCorentin Chary 
13416f6ae06eSCorentin Chary 	if (debug && samsung->sdiag[0])
13426f6ae06eSCorentin Chary 		pr_info("sdiag: %s", samsung->sdiag);
13436f6ae06eSCorentin Chary }
13446f6ae06eSCorentin Chary 
13455dea7a20SCorentin Chary static int __init samsung_sabi_init(struct samsung_laptop *samsung)
13465dea7a20SCorentin Chary {
13475dea7a20SCorentin Chary 	const struct sabi_config *config = NULL;
13485dea7a20SCorentin Chary 	const struct sabi_commands *commands;
13495dea7a20SCorentin Chary 	unsigned int ifaceP;
13505dea7a20SCorentin Chary 	int ret = 0;
13515dea7a20SCorentin Chary 	int i;
13525dea7a20SCorentin Chary 	int loca;
13535dea7a20SCorentin Chary 
13545dea7a20SCorentin Chary 	samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
13555dea7a20SCorentin Chary 	if (!samsung->f0000_segment) {
13563be324a9SCorentin Chary 		if (debug || force)
13575dea7a20SCorentin Chary 			pr_err("Can't map the segment at 0xf0000\n");
13585dea7a20SCorentin Chary 		ret = -EINVAL;
13595dea7a20SCorentin Chary 		goto exit;
13605dea7a20SCorentin Chary 	}
13615dea7a20SCorentin Chary 
13626f6ae06eSCorentin Chary 	samsung_sabi_diag(samsung);
13636f6ae06eSCorentin Chary 
13645dea7a20SCorentin Chary 	/* Try to find one of the signatures in memory to find the header */
13655dea7a20SCorentin Chary 	for (i = 0; sabi_configs[i].test_string != 0; ++i) {
13665dea7a20SCorentin Chary 		samsung->config = &sabi_configs[i];
13675dea7a20SCorentin Chary 		loca = find_signature(samsung->f0000_segment,
13685dea7a20SCorentin Chary 				      samsung->config->test_string);
13695dea7a20SCorentin Chary 		if (loca != 0xffff)
13705dea7a20SCorentin Chary 			break;
13715dea7a20SCorentin Chary 	}
13725dea7a20SCorentin Chary 
13735dea7a20SCorentin Chary 	if (loca == 0xffff) {
13743be324a9SCorentin Chary 		if (debug || force)
13755dea7a20SCorentin Chary 			pr_err("This computer does not support SABI\n");
13765dea7a20SCorentin Chary 		ret = -ENODEV;
13775dea7a20SCorentin Chary 		goto exit;
13785dea7a20SCorentin Chary 	}
13795dea7a20SCorentin Chary 
13805dea7a20SCorentin Chary 	config = samsung->config;
13815dea7a20SCorentin Chary 	commands = &config->commands;
13825dea7a20SCorentin Chary 
13835dea7a20SCorentin Chary 	/* point to the SMI port Number */
13845dea7a20SCorentin Chary 	loca += 1;
13855dea7a20SCorentin Chary 	samsung->sabi = (samsung->f0000_segment + loca);
13865dea7a20SCorentin Chary 
13875dea7a20SCorentin Chary 	/* Get a pointer to the SABI Interface */
13885dea7a20SCorentin Chary 	ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
13895dea7a20SCorentin Chary 	ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
139049dd7730SCorentin Chary 
139149dd7730SCorentin Chary 	if (debug)
139249dd7730SCorentin Chary 		samsung_sabi_infos(samsung, loca, ifaceP);
139349dd7730SCorentin Chary 
13945dea7a20SCorentin Chary 	samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
13955dea7a20SCorentin Chary 	if (!samsung->sabi_iface) {
13965dea7a20SCorentin Chary 		pr_err("Can't remap %x\n", ifaceP);
13975dea7a20SCorentin Chary 		ret = -EINVAL;
13985dea7a20SCorentin Chary 		goto exit;
13995dea7a20SCorentin Chary 	}
14005dea7a20SCorentin Chary 
14015dea7a20SCorentin Chary 	/* Turn on "Linux" mode in the BIOS */
14025dea7a20SCorentin Chary 	if (commands->set_linux != 0xff) {
14037e960711SCorentin Chary 		int retval = sabi_set_commandb(samsung,
14045dea7a20SCorentin Chary 					       commands->set_linux, 0x81);
14055dea7a20SCorentin Chary 		if (retval) {
14065dea7a20SCorentin Chary 			pr_warn("Linux mode was not set!\n");
14075dea7a20SCorentin Chary 			ret = -ENODEV;
14085dea7a20SCorentin Chary 			goto exit;
14095dea7a20SCorentin Chary 		}
14105dea7a20SCorentin Chary 	}
14115dea7a20SCorentin Chary 
14125dea7a20SCorentin Chary 	/* Check for stepping quirk */
1413f34cd9caSCorentin Chary 	if (samsung->handle_backlight)
14145dea7a20SCorentin Chary 		check_for_stepping_quirk(samsung);
14155dea7a20SCorentin Chary 
14162e777187SCorentin Chary 	pr_info("detected SABI interface: %s\n",
14172e777187SCorentin Chary 		samsung->config->test_string);
14182e777187SCorentin Chary 
14195dea7a20SCorentin Chary exit:
14205dea7a20SCorentin Chary 	if (ret)
14215dea7a20SCorentin Chary 		samsung_sabi_exit(samsung);
14225dea7a20SCorentin Chary 
14235dea7a20SCorentin Chary 	return ret;
14245dea7a20SCorentin Chary }
14255dea7a20SCorentin Chary 
14265dea7a20SCorentin Chary static void samsung_platform_exit(struct samsung_laptop *samsung)
14275dea7a20SCorentin Chary {
14285dea7a20SCorentin Chary 	if (samsung->platform_device) {
14295dea7a20SCorentin Chary 		platform_device_unregister(samsung->platform_device);
14305dea7a20SCorentin Chary 		samsung->platform_device = NULL;
14315dea7a20SCorentin Chary 	}
14325dea7a20SCorentin Chary }
14335dea7a20SCorentin Chary 
14340ca849eaSScott Thrasher static int samsung_pm_notification(struct notifier_block *nb,
14350ca849eaSScott Thrasher 				   unsigned long val, void *ptr)
14360ca849eaSScott Thrasher {
14370ca849eaSScott Thrasher 	struct samsung_laptop *samsung;
14380ca849eaSScott Thrasher 
14390ca849eaSScott Thrasher 	samsung = container_of(nb, struct samsung_laptop, pm_nb);
14400ca849eaSScott Thrasher 	if (val == PM_POST_HIBERNATION &&
14410ca849eaSScott Thrasher 	    samsung->quirks->enable_kbd_backlight)
14420ca849eaSScott Thrasher 		kbd_backlight_enable(samsung);
14430ca849eaSScott Thrasher 
14440ca849eaSScott Thrasher 	return 0;
14450ca849eaSScott Thrasher }
14460ca849eaSScott Thrasher 
14475dea7a20SCorentin Chary static int __init samsung_platform_init(struct samsung_laptop *samsung)
14485dea7a20SCorentin Chary {
14495dea7a20SCorentin Chary 	struct platform_device *pdev;
14505dea7a20SCorentin Chary 
14515dea7a20SCorentin Chary 	pdev = platform_device_register_simple("samsung", -1, NULL, 0);
14525dea7a20SCorentin Chary 	if (IS_ERR(pdev))
14535dea7a20SCorentin Chary 		return PTR_ERR(pdev);
14545dea7a20SCorentin Chary 
14555dea7a20SCorentin Chary 	samsung->platform_device = pdev;
14565dea7a20SCorentin Chary 	platform_set_drvdata(samsung->platform_device, samsung);
14575dea7a20SCorentin Chary 	return 0;
14585dea7a20SCorentin Chary }
14595dea7a20SCorentin Chary 
1460a979e2e2SCorentin Chary static struct samsung_quirks *quirks;
1461a979e2e2SCorentin Chary 
1462a979e2e2SCorentin Chary static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1463a979e2e2SCorentin Chary {
1464a979e2e2SCorentin Chary 	quirks = d->driver_data;
1465a979e2e2SCorentin Chary 	return 0;
1466a979e2e2SCorentin Chary }
1467a979e2e2SCorentin Chary 
14682d70b73aSGreg Kroah-Hartman static struct dmi_system_id __initdata samsung_dmi_table[] = {
14692d70b73aSGreg Kroah-Hartman 	{
14702d70b73aSGreg Kroah-Hartman 		.matches = {
14712d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
14722d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
14733be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
14742d70b73aSGreg Kroah-Hartman 		},
14752d70b73aSGreg Kroah-Hartman 	},
14762d70b73aSGreg Kroah-Hartman 	{
14772d70b73aSGreg Kroah-Hartman 		.matches = {
14782d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
14792d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
14803be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
14812d70b73aSGreg Kroah-Hartman 		},
14822d70b73aSGreg Kroah-Hartman 	},
14832d70b73aSGreg Kroah-Hartman 	{
14844e2441c0SJ Witteveen 		.matches = {
14854e2441c0SJ Witteveen 			DMI_MATCH(DMI_SYS_VENDOR,
14864e2441c0SJ Witteveen 					"SAMSUNG ELECTRONICS CO., LTD."),
14873be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
14884e2441c0SJ Witteveen 		},
14894e2441c0SJ Witteveen 	},
14904e2441c0SJ Witteveen 	{
14912d70b73aSGreg Kroah-Hartman 		.matches = {
14922d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
14932d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
14943be324a9SCorentin Chary 			DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
14952d70b73aSGreg Kroah-Hartman 		},
14967500eeb0STommaso Massimi 	},
1497e052067dSCorentin Chary 	/* DMI ids for laptops with bad Chassis Type */
1498e052067dSCorentin Chary 	{
1499e052067dSCorentin Chary 	  .ident = "R40/R41",
1500e052067dSCorentin Chary 	  .matches = {
1501e052067dSCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1502e052067dSCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1503e052067dSCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1504e052067dSCorentin Chary 		},
1505e052067dSCorentin Chary 	},
1506a979e2e2SCorentin Chary 	/* Specific DMI ids for laptop with quirks */
1507a979e2e2SCorentin Chary 	{
1508a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1509a979e2e2SCorentin Chary 	 .ident = "N150P",
1510a979e2e2SCorentin Chary 	 .matches = {
1511a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1512a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
1513a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N150P"),
1514a979e2e2SCorentin Chary 		},
15154690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1516a979e2e2SCorentin Chary 	},
1517a979e2e2SCorentin Chary 	{
1518a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1519a979e2e2SCorentin Chary 	 .ident = "N145P/N250P/N260P",
1520a979e2e2SCorentin Chary 	 .matches = {
1521a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1522a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1523a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1524a979e2e2SCorentin Chary 		},
15254690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1526a979e2e2SCorentin Chary 	},
1527a979e2e2SCorentin Chary 	{
1528a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1529a979e2e2SCorentin Chary 	 .ident = "N150/N210/N220",
1530a979e2e2SCorentin Chary 	 .matches = {
1531a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1532a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1533a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1534a979e2e2SCorentin Chary 		},
1535a979e2e2SCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
1536a979e2e2SCorentin Chary 	},
1537a979e2e2SCorentin Chary 	{
1538a979e2e2SCorentin Chary 	 .callback = samsung_dmi_matched,
1539a979e2e2SCorentin Chary 	 .ident = "NF110/NF210/NF310",
1540a979e2e2SCorentin Chary 	 .matches = {
1541a979e2e2SCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1542a979e2e2SCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1543a979e2e2SCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1544a979e2e2SCorentin Chary 		},
1545a979e2e2SCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
1546a979e2e2SCorentin Chary 	},
154709d5677cSCorentin Chary 	{
154809d5677cSCorentin Chary 	 .callback = samsung_dmi_matched,
154909d5677cSCorentin Chary 	 .ident = "X360",
155009d5677cSCorentin Chary 	 .matches = {
155109d5677cSCorentin Chary 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
155209d5677cSCorentin Chary 		DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
155309d5677cSCorentin Chary 		DMI_MATCH(DMI_BOARD_NAME, "X360"),
155409d5677cSCorentin Chary 		},
155509d5677cSCorentin Chary 	 .driver_data = &samsung_broken_acpi_video,
155609d5677cSCorentin Chary 	},
1557e04c200fSSeth Forshee 	{
1558e04c200fSSeth Forshee 	 .callback = samsung_dmi_matched,
1559e04c200fSSeth Forshee 	 .ident = "N250P",
1560e04c200fSSeth Forshee 	 .matches = {
1561e04c200fSSeth Forshee 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1562e04c200fSSeth Forshee 		DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
1563e04c200fSSeth Forshee 		DMI_MATCH(DMI_BOARD_NAME, "N250P"),
1564e04c200fSSeth Forshee 		},
15654690555eSHans de Goede 	 .driver_data = &samsung_use_native_backlight,
1566e04c200fSSeth Forshee 	},
15670ca849eaSScott Thrasher 	{
15680ca849eaSScott Thrasher 	 .callback = samsung_dmi_matched,
15695a1426c9SHans de Goede 	 .ident = "NC210",
15705a1426c9SHans de Goede 	 .matches = {
15715a1426c9SHans de Goede 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
15725a1426c9SHans de Goede 		DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
15735a1426c9SHans de Goede 		DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
15745a1426c9SHans de Goede 		},
15755a1426c9SHans de Goede 	 .driver_data = &samsung_broken_acpi_video,
15765a1426c9SHans de Goede 	},
15775a1426c9SHans de Goede 	{
15785a1426c9SHans de Goede 	 .callback = samsung_dmi_matched,
15790ca849eaSScott Thrasher 	 .ident = "730U3E/740U3E",
15800ca849eaSScott Thrasher 	 .matches = {
15810ca849eaSScott Thrasher 		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
15820ca849eaSScott Thrasher 		DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
15830ca849eaSScott Thrasher 		},
15840ca849eaSScott Thrasher 	 .driver_data = &samsung_np740u3e,
15850ca849eaSScott Thrasher 	},
15862d70b73aSGreg Kroah-Hartman 	{ },
15872d70b73aSGreg Kroah-Hartman };
15882d70b73aSGreg Kroah-Hartman MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
15892d70b73aSGreg Kroah-Hartman 
15905dea7a20SCorentin Chary static struct platform_device *samsung_platform_device;
15912d70b73aSGreg Kroah-Hartman 
15922d70b73aSGreg Kroah-Hartman static int __init samsung_init(void)
15932d70b73aSGreg Kroah-Hartman {
15945dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
15955dea7a20SCorentin Chary 	int ret;
15962d70b73aSGreg Kroah-Hartman 
1597e0094244SMatt Fleming 	if (efi_enabled(EFI_BOOT))
1598e0094244SMatt Fleming 		return -ENODEV;
1599e0094244SMatt Fleming 
1600a979e2e2SCorentin Chary 	quirks = &samsung_unknown;
16012d70b73aSGreg Kroah-Hartman 	if (!force && !dmi_check_system(samsung_dmi_table))
16022d70b73aSGreg Kroah-Hartman 		return -ENODEV;
16032d70b73aSGreg Kroah-Hartman 
1604a6df4894SCorentin Chary 	samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1605a6df4894SCorentin Chary 	if (!samsung)
1606a6df4894SCorentin Chary 		return -ENOMEM;
1607a6df4894SCorentin Chary 
1608a6df4894SCorentin Chary 	mutex_init(&samsung->sabi_mutex);
1609f34cd9caSCorentin Chary 	samsung->handle_backlight = true;
1610a979e2e2SCorentin Chary 	samsung->quirks = quirks;
1611f34cd9caSCorentin Chary 
1612a979e2e2SCorentin Chary 
1613a60b2176SCorentin Chary #ifdef CONFIG_ACPI
1614a60b2176SCorentin Chary 	if (samsung->quirks->broken_acpi_video)
1615a60b2176SCorentin Chary 		acpi_video_dmi_promote_vendor();
1616a60b2176SCorentin Chary 
1617f34cd9caSCorentin Chary 	/* Don't handle backlight here if the acpi video already handle it */
1618a979e2e2SCorentin Chary 	if (acpi_video_backlight_support()) {
1619f34cd9caSCorentin Chary 		samsung->handle_backlight = false;
1620a60b2176SCorentin Chary 	} else if (samsung->quirks->broken_acpi_video) {
1621a60b2176SCorentin Chary 		pr_info("Disabling ACPI video driver\n");
1622a60b2176SCorentin Chary 		acpi_video_unregister();
1623a979e2e2SCorentin Chary 	}
16244690555eSHans de Goede 
16254690555eSHans de Goede 	if (samsung->quirks->use_native_backlight) {
16264690555eSHans de Goede 		pr_info("Using native backlight driver\n");
16274690555eSHans de Goede 		/* Tell acpi-video to not handle the backlight */
16284690555eSHans de Goede 		acpi_video_dmi_promote_vendor();
16294690555eSHans de Goede 		acpi_video_unregister();
16304690555eSHans de Goede 		/* And also do not handle it ourselves */
16314690555eSHans de Goede 		samsung->handle_backlight = false;
16324690555eSHans de Goede 	}
1633f34cd9caSCorentin Chary #endif
1634a979e2e2SCorentin Chary 
16355dea7a20SCorentin Chary 	ret = samsung_platform_init(samsung);
16365dea7a20SCorentin Chary 	if (ret)
16375dea7a20SCorentin Chary 		goto error_platform;
16382d70b73aSGreg Kroah-Hartman 
16395dea7a20SCorentin Chary 	ret = samsung_sabi_init(samsung);
16405dea7a20SCorentin Chary 	if (ret)
16415dea7a20SCorentin Chary 		goto error_sabi;
16422d70b73aSGreg Kroah-Hartman 
16433be324a9SCorentin Chary #ifdef CONFIG_ACPI
16443be324a9SCorentin Chary 	/* Only log that if we are really on a sabi platform */
1645a60b2176SCorentin Chary 	if (acpi_video_backlight_support())
16463be324a9SCorentin Chary 		pr_info("Backlight controlled by ACPI video driver\n");
16473be324a9SCorentin Chary #endif
16483be324a9SCorentin Chary 
16495dea7a20SCorentin Chary 	ret = samsung_sysfs_init(samsung);
16505dea7a20SCorentin Chary 	if (ret)
16515dea7a20SCorentin Chary 		goto error_sysfs;
16522d70b73aSGreg Kroah-Hartman 
16535dea7a20SCorentin Chary 	ret = samsung_backlight_init(samsung);
16545dea7a20SCorentin Chary 	if (ret)
16555dea7a20SCorentin Chary 		goto error_backlight;
1656a6df4894SCorentin Chary 
16575dea7a20SCorentin Chary 	ret = samsung_rfkill_init(samsung);
16585dea7a20SCorentin Chary 	if (ret)
16595dea7a20SCorentin Chary 		goto error_rfkill;
16602d70b73aSGreg Kroah-Hartman 
1661f674ebf1SCorentin Chary 	ret = samsung_leds_init(samsung);
1662f674ebf1SCorentin Chary 	if (ret)
1663f674ebf1SCorentin Chary 		goto error_leds;
1664f674ebf1SCorentin Chary 
16655b80fc40SCorentin Chary 	ret = samsung_debugfs_init(samsung);
16665b80fc40SCorentin Chary 	if (ret)
16675b80fc40SCorentin Chary 		goto error_debugfs;
16685b80fc40SCorentin Chary 
16690ca849eaSScott Thrasher 	samsung->pm_nb.notifier_call = samsung_pm_notification;
16700ca849eaSScott Thrasher 	register_pm_notifier(&samsung->pm_nb);
16710ca849eaSScott Thrasher 
16725dea7a20SCorentin Chary 	samsung_platform_device = samsung->platform_device;
16735dea7a20SCorentin Chary 	return ret;
16742d70b73aSGreg Kroah-Hartman 
16755b80fc40SCorentin Chary error_debugfs:
1676f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
1677f674ebf1SCorentin Chary error_leds:
16785b80fc40SCorentin Chary 	samsung_rfkill_exit(samsung);
16795dea7a20SCorentin Chary error_rfkill:
16805dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
16815dea7a20SCorentin Chary error_backlight:
16825dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
16835dea7a20SCorentin Chary error_sysfs:
16845dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
16855dea7a20SCorentin Chary error_sabi:
16865dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
16875dea7a20SCorentin Chary error_platform:
1688a6df4894SCorentin Chary 	kfree(samsung);
16895dea7a20SCorentin Chary 	return ret;
16902d70b73aSGreg Kroah-Hartman }
16912d70b73aSGreg Kroah-Hartman 
16922d70b73aSGreg Kroah-Hartman static void __exit samsung_exit(void)
16932d70b73aSGreg Kroah-Hartman {
16945dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
16952d70b73aSGreg Kroah-Hartman 
16965dea7a20SCorentin Chary 	samsung = platform_get_drvdata(samsung_platform_device);
16970ca849eaSScott Thrasher 	unregister_pm_notifier(&samsung->pm_nb);
1698a6df4894SCorentin Chary 
16995b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
1700f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
17015dea7a20SCorentin Chary 	samsung_rfkill_exit(samsung);
17025dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
17035dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
17045dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
17055dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
17065dea7a20SCorentin Chary 
1707a6df4894SCorentin Chary 	kfree(samsung);
17085dea7a20SCorentin Chary 	samsung_platform_device = NULL;
17092d70b73aSGreg Kroah-Hartman }
17102d70b73aSGreg Kroah-Hartman 
17112d70b73aSGreg Kroah-Hartman module_init(samsung_init);
17122d70b73aSGreg Kroah-Hartman module_exit(samsung_exit);
17132d70b73aSGreg Kroah-Hartman 
17142d70b73aSGreg Kroah-Hartman MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
17152d70b73aSGreg Kroah-Hartman MODULE_DESCRIPTION("Samsung Backlight driver");
17162d70b73aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
1717