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>
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 
487e960711SCorentin Chary /* Structure get/set data using sabi */
497e960711SCorentin Chary struct sabi_data {
507e960711SCorentin Chary 	union {
517e960711SCorentin Chary 		struct {
527e960711SCorentin Chary 			u32 d0;
537e960711SCorentin Chary 			u32 d1;
547e960711SCorentin Chary 			u16 d2;
557e960711SCorentin Chary 			u8  d3;
567e960711SCorentin Chary 		};
577e960711SCorentin Chary 		u8 data[11];
587e960711SCorentin Chary 	};
592d70b73aSGreg Kroah-Hartman };
602d70b73aSGreg Kroah-Hartman 
612d70b73aSGreg Kroah-Hartman struct sabi_header_offsets {
622d70b73aSGreg Kroah-Hartman 	u8 port;
632d70b73aSGreg Kroah-Hartman 	u8 re_mem;
642d70b73aSGreg Kroah-Hartman 	u8 iface_func;
652d70b73aSGreg Kroah-Hartman 	u8 en_mem;
662d70b73aSGreg Kroah-Hartman 	u8 data_offset;
672d70b73aSGreg Kroah-Hartman 	u8 data_segment;
682d70b73aSGreg Kroah-Hartman };
692d70b73aSGreg Kroah-Hartman 
702d70b73aSGreg Kroah-Hartman struct sabi_commands {
712d70b73aSGreg Kroah-Hartman 	/*
722d70b73aSGreg Kroah-Hartman 	 * Brightness is 0 - 8, as described above.
732d70b73aSGreg Kroah-Hartman 	 * Value 0 is for the BIOS to use
742d70b73aSGreg Kroah-Hartman 	 */
757e960711SCorentin Chary 	u16 get_brightness;
767e960711SCorentin Chary 	u16 set_brightness;
772d70b73aSGreg Kroah-Hartman 
782d70b73aSGreg Kroah-Hartman 	/*
792d70b73aSGreg Kroah-Hartman 	 * first byte:
802d70b73aSGreg Kroah-Hartman 	 * 0x00 - wireless is off
812d70b73aSGreg Kroah-Hartman 	 * 0x01 - wireless is on
822d70b73aSGreg Kroah-Hartman 	 * second byte:
832d70b73aSGreg Kroah-Hartman 	 * 0x02 - 3G is off
842d70b73aSGreg Kroah-Hartman 	 * 0x03 - 3G is on
852d70b73aSGreg Kroah-Hartman 	 * TODO, verify 3G is correct, that doesn't seem right...
862d70b73aSGreg Kroah-Hartman 	 */
877e960711SCorentin Chary 	u16 get_wireless_button;
887e960711SCorentin Chary 	u16 set_wireless_button;
892d70b73aSGreg Kroah-Hartman 
902d70b73aSGreg Kroah-Hartman 	/* 0 is off, 1 is on */
917e960711SCorentin Chary 	u16 get_backlight;
927e960711SCorentin Chary 	u16 set_backlight;
932d70b73aSGreg Kroah-Hartman 
942d70b73aSGreg Kroah-Hartman 	/*
952d70b73aSGreg Kroah-Hartman 	 * 0x80 or 0x00 - no action
962d70b73aSGreg Kroah-Hartman 	 * 0x81 - recovery key pressed
972d70b73aSGreg Kroah-Hartman 	 */
987e960711SCorentin Chary 	u16 get_recovery_mode;
997e960711SCorentin Chary 	u16 set_recovery_mode;
1002d70b73aSGreg Kroah-Hartman 
1012d70b73aSGreg Kroah-Hartman 	/*
1022d70b73aSGreg Kroah-Hartman 	 * on seclinux: 0 is low, 1 is high,
1032d70b73aSGreg Kroah-Hartman 	 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
1042d70b73aSGreg Kroah-Hartman 	 */
1057e960711SCorentin Chary 	u16 get_performance_level;
1067e960711SCorentin Chary 	u16 set_performance_level;
1072d70b73aSGreg Kroah-Hartman 
108cb5b5c91SCorentin Chary 	/* 0x80 is off, 0x81 is on */
109cb5b5c91SCorentin Chary 	u16 get_battery_life_extender;
110cb5b5c91SCorentin Chary 	u16 set_battery_life_extender;
111cb5b5c91SCorentin Chary 
1123a75d378SCorentin Chary 	/* 0x80 is off, 0x81 is on */
1133a75d378SCorentin Chary 	u16 get_usb_charge;
1143a75d378SCorentin Chary 	u16 set_usb_charge;
1153a75d378SCorentin Chary 
116f674ebf1SCorentin Chary 	/* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
117f674ebf1SCorentin Chary 	u16 kbd_backlight;
118f674ebf1SCorentin Chary 
1192d70b73aSGreg Kroah-Hartman 	/*
1202d70b73aSGreg Kroah-Hartman 	 * Tell the BIOS that Linux is running on this machine.
1212d70b73aSGreg Kroah-Hartman 	 * 81 is on, 80 is off
1222d70b73aSGreg Kroah-Hartman 	 */
1237e960711SCorentin Chary 	u16 set_linux;
1242d70b73aSGreg Kroah-Hartman };
1252d70b73aSGreg Kroah-Hartman 
1262d70b73aSGreg Kroah-Hartman struct sabi_performance_level {
1272d70b73aSGreg Kroah-Hartman 	const char *name;
1287e960711SCorentin Chary 	u16 value;
1292d70b73aSGreg Kroah-Hartman };
1302d70b73aSGreg Kroah-Hartman 
1312d70b73aSGreg Kroah-Hartman struct sabi_config {
1322d70b73aSGreg Kroah-Hartman 	const char *test_string;
1332d70b73aSGreg Kroah-Hartman 	u16 main_function;
1342d70b73aSGreg Kroah-Hartman 	const struct sabi_header_offsets header_offsets;
1352d70b73aSGreg Kroah-Hartman 	const struct sabi_commands commands;
1362d70b73aSGreg Kroah-Hartman 	const struct sabi_performance_level performance_levels[4];
1372d70b73aSGreg Kroah-Hartman 	u8 min_brightness;
1382d70b73aSGreg Kroah-Hartman 	u8 max_brightness;
1392d70b73aSGreg Kroah-Hartman };
1402d70b73aSGreg Kroah-Hartman 
1412d70b73aSGreg Kroah-Hartman static const struct sabi_config sabi_configs[] = {
1422d70b73aSGreg Kroah-Hartman 	{
1432d70b73aSGreg Kroah-Hartman 		.test_string = "SECLINUX",
1442d70b73aSGreg Kroah-Hartman 
1452d70b73aSGreg Kroah-Hartman 		.main_function = 0x4c49,
1462d70b73aSGreg Kroah-Hartman 
1472d70b73aSGreg Kroah-Hartman 		.header_offsets = {
1482d70b73aSGreg Kroah-Hartman 			.port = 0x00,
1492d70b73aSGreg Kroah-Hartman 			.re_mem = 0x02,
1502d70b73aSGreg Kroah-Hartman 			.iface_func = 0x03,
1512d70b73aSGreg Kroah-Hartman 			.en_mem = 0x04,
1522d70b73aSGreg Kroah-Hartman 			.data_offset = 0x05,
1532d70b73aSGreg Kroah-Hartman 			.data_segment = 0x07,
1542d70b73aSGreg Kroah-Hartman 		},
1552d70b73aSGreg Kroah-Hartman 
1562d70b73aSGreg Kroah-Hartman 		.commands = {
1572d70b73aSGreg Kroah-Hartman 			.get_brightness = 0x00,
1582d70b73aSGreg Kroah-Hartman 			.set_brightness = 0x01,
1592d70b73aSGreg Kroah-Hartman 
1602d70b73aSGreg Kroah-Hartman 			.get_wireless_button = 0x02,
1612d70b73aSGreg Kroah-Hartman 			.set_wireless_button = 0x03,
1622d70b73aSGreg Kroah-Hartman 
1632d70b73aSGreg Kroah-Hartman 			.get_backlight = 0x04,
1642d70b73aSGreg Kroah-Hartman 			.set_backlight = 0x05,
1652d70b73aSGreg Kroah-Hartman 
1662d70b73aSGreg Kroah-Hartman 			.get_recovery_mode = 0x06,
1672d70b73aSGreg Kroah-Hartman 			.set_recovery_mode = 0x07,
1682d70b73aSGreg Kroah-Hartman 
1692d70b73aSGreg Kroah-Hartman 			.get_performance_level = 0x08,
1702d70b73aSGreg Kroah-Hartman 			.set_performance_level = 0x09,
1712d70b73aSGreg Kroah-Hartman 
172cb5b5c91SCorentin Chary 			.get_battery_life_extender = 0xFFFF,
173cb5b5c91SCorentin Chary 			.set_battery_life_extender = 0xFFFF,
174cb5b5c91SCorentin Chary 
1753a75d378SCorentin Chary 			.get_usb_charge = 0xFFFF,
1763a75d378SCorentin Chary 			.set_usb_charge = 0xFFFF,
1773a75d378SCorentin Chary 
178f674ebf1SCorentin Chary 			.kbd_backlight = 0xFFFF,
179f674ebf1SCorentin Chary 
1802d70b73aSGreg Kroah-Hartman 			.set_linux = 0x0a,
1812d70b73aSGreg Kroah-Hartman 		},
1822d70b73aSGreg Kroah-Hartman 
1832d70b73aSGreg Kroah-Hartman 		.performance_levels = {
1842d70b73aSGreg Kroah-Hartman 			{
1852d70b73aSGreg Kroah-Hartman 				.name = "silent",
1862d70b73aSGreg Kroah-Hartman 				.value = 0,
1872d70b73aSGreg Kroah-Hartman 			},
1882d70b73aSGreg Kroah-Hartman 			{
1892d70b73aSGreg Kroah-Hartman 				.name = "normal",
1902d70b73aSGreg Kroah-Hartman 				.value = 1,
1912d70b73aSGreg Kroah-Hartman 			},
1922d70b73aSGreg Kroah-Hartman 			{ },
1932d70b73aSGreg Kroah-Hartman 		},
1942d70b73aSGreg Kroah-Hartman 		.min_brightness = 1,
1952d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
1962d70b73aSGreg Kroah-Hartman 	},
1972d70b73aSGreg Kroah-Hartman 	{
1982d70b73aSGreg Kroah-Hartman 		.test_string = "SwSmi@",
1992d70b73aSGreg Kroah-Hartman 
2002d70b73aSGreg Kroah-Hartman 		.main_function = 0x5843,
2012d70b73aSGreg Kroah-Hartman 
2022d70b73aSGreg Kroah-Hartman 		.header_offsets = {
2032d70b73aSGreg Kroah-Hartman 			.port = 0x00,
2042d70b73aSGreg Kroah-Hartman 			.re_mem = 0x04,
2052d70b73aSGreg Kroah-Hartman 			.iface_func = 0x02,
2062d70b73aSGreg Kroah-Hartman 			.en_mem = 0x03,
2072d70b73aSGreg Kroah-Hartman 			.data_offset = 0x05,
2082d70b73aSGreg Kroah-Hartman 			.data_segment = 0x07,
2092d70b73aSGreg Kroah-Hartman 		},
2102d70b73aSGreg Kroah-Hartman 
2112d70b73aSGreg Kroah-Hartman 		.commands = {
2122d70b73aSGreg Kroah-Hartman 			.get_brightness = 0x10,
2132d70b73aSGreg Kroah-Hartman 			.set_brightness = 0x11,
2142d70b73aSGreg Kroah-Hartman 
2152d70b73aSGreg Kroah-Hartman 			.get_wireless_button = 0x12,
2162d70b73aSGreg Kroah-Hartman 			.set_wireless_button = 0x13,
2172d70b73aSGreg Kroah-Hartman 
2182d70b73aSGreg Kroah-Hartman 			.get_backlight = 0x2d,
2192d70b73aSGreg Kroah-Hartman 			.set_backlight = 0x2e,
2202d70b73aSGreg Kroah-Hartman 
2212d70b73aSGreg Kroah-Hartman 			.get_recovery_mode = 0xff,
2222d70b73aSGreg Kroah-Hartman 			.set_recovery_mode = 0xff,
2232d70b73aSGreg Kroah-Hartman 
2242d70b73aSGreg Kroah-Hartman 			.get_performance_level = 0x31,
2252d70b73aSGreg Kroah-Hartman 			.set_performance_level = 0x32,
2262d70b73aSGreg Kroah-Hartman 
227cb5b5c91SCorentin Chary 			.get_battery_life_extender = 0x65,
228cb5b5c91SCorentin Chary 			.set_battery_life_extender = 0x66,
229cb5b5c91SCorentin Chary 
2303a75d378SCorentin Chary 			.get_usb_charge = 0x67,
2313a75d378SCorentin Chary 			.set_usb_charge = 0x68,
2323a75d378SCorentin Chary 
233f674ebf1SCorentin Chary 			.kbd_backlight = 0x78,
234f674ebf1SCorentin Chary 
2352d70b73aSGreg Kroah-Hartman 			.set_linux = 0xff,
2362d70b73aSGreg Kroah-Hartman 		},
2372d70b73aSGreg Kroah-Hartman 
2382d70b73aSGreg Kroah-Hartman 		.performance_levels = {
2392d70b73aSGreg Kroah-Hartman 			{
2402d70b73aSGreg Kroah-Hartman 				.name = "normal",
2412d70b73aSGreg Kroah-Hartman 				.value = 0,
2422d70b73aSGreg Kroah-Hartman 			},
2432d70b73aSGreg Kroah-Hartman 			{
2442d70b73aSGreg Kroah-Hartman 				.name = "silent",
2452d70b73aSGreg Kroah-Hartman 				.value = 1,
2462d70b73aSGreg Kroah-Hartman 			},
2472d70b73aSGreg Kroah-Hartman 			{
2482d70b73aSGreg Kroah-Hartman 				.name = "overclock",
2492d70b73aSGreg Kroah-Hartman 				.value = 2,
2502d70b73aSGreg Kroah-Hartman 			},
2512d70b73aSGreg Kroah-Hartman 			{ },
2522d70b73aSGreg Kroah-Hartman 		},
2532d70b73aSGreg Kroah-Hartman 		.min_brightness = 0,
2542d70b73aSGreg Kroah-Hartman 		.max_brightness = 8,
2552d70b73aSGreg Kroah-Hartman 	},
2562d70b73aSGreg Kroah-Hartman 	{ },
2572d70b73aSGreg Kroah-Hartman };
2582d70b73aSGreg Kroah-Hartman 
2595b80fc40SCorentin Chary /*
2605b80fc40SCorentin Chary  * samsung-laptop/    - debugfs root directory
2615b80fc40SCorentin Chary  *   f0000_segment    - dump f0000 segment
2625b80fc40SCorentin Chary  *   command          - current command
2635b80fc40SCorentin Chary  *   data             - current data
2645b80fc40SCorentin Chary  *   d0, d1, d2, d3   - data fields
2655b80fc40SCorentin Chary  *   call             - call SABI using command and data
2665b80fc40SCorentin Chary  *
2675b80fc40SCorentin Chary  * This allow to call arbitrary sabi commands wihout
2685b80fc40SCorentin Chary  * modifying the driver at all.
2695b80fc40SCorentin Chary  * For example, setting the keyboard backlight brightness to 5
2705b80fc40SCorentin Chary  *
2715b80fc40SCorentin Chary  *  echo 0x78 > command
2725b80fc40SCorentin Chary  *  echo 0x0582 > d0
2735b80fc40SCorentin Chary  *  echo 0 > d1
2745b80fc40SCorentin Chary  *  echo 0 > d2
2755b80fc40SCorentin Chary  *  echo 0 > d3
2765b80fc40SCorentin Chary  *  cat call
2775b80fc40SCorentin Chary  */
2785b80fc40SCorentin Chary 
2795b80fc40SCorentin Chary struct samsung_laptop_debug {
2805b80fc40SCorentin Chary 	struct dentry *root;
2815b80fc40SCorentin Chary 	struct sabi_data data;
2825b80fc40SCorentin Chary 	u16 command;
2835b80fc40SCorentin Chary 
2845b80fc40SCorentin Chary 	struct debugfs_blob_wrapper f0000_wrapper;
2855b80fc40SCorentin Chary 	struct debugfs_blob_wrapper data_wrapper;
2865b80fc40SCorentin Chary };
2875b80fc40SCorentin Chary 
288a6df4894SCorentin Chary struct samsung_laptop {
289a6df4894SCorentin Chary 	const struct sabi_config *config;
2902d70b73aSGreg Kroah-Hartman 
291a6df4894SCorentin Chary 	void __iomem *sabi;
292a6df4894SCorentin Chary 	void __iomem *sabi_iface;
293a6df4894SCorentin Chary 	void __iomem *f0000_segment;
294a6df4894SCorentin Chary 
295a6df4894SCorentin Chary 	struct mutex sabi_mutex;
296a6df4894SCorentin Chary 
2975dea7a20SCorentin Chary 	struct platform_device *platform_device;
298a6df4894SCorentin Chary 	struct backlight_device *backlight_device;
299a6df4894SCorentin Chary 	struct rfkill *rfk;
300a6df4894SCorentin Chary 
301f674ebf1SCorentin Chary 	struct led_classdev kbd_led;
302f674ebf1SCorentin Chary 	int kbd_led_wk;
303f674ebf1SCorentin Chary 	struct workqueue_struct *led_workqueue;
304f674ebf1SCorentin Chary 	struct work_struct kbd_led_work;
305f674ebf1SCorentin Chary 
3065b80fc40SCorentin Chary 	struct samsung_laptop_debug debug;
3075b80fc40SCorentin Chary 
308f34cd9caSCorentin Chary 	bool handle_backlight;
309a6df4894SCorentin Chary 	bool has_stepping_quirk;
310a6df4894SCorentin Chary };
311a6df4894SCorentin Chary 
3125dea7a20SCorentin Chary 
3132d70b73aSGreg Kroah-Hartman 
31490ab5ee9SRusty Russell static bool force;
3152d70b73aSGreg Kroah-Hartman module_param(force, bool, 0);
3162d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(force,
3172d70b73aSGreg Kroah-Hartman 		"Disable the DMI check and forces the driver to be loaded");
3182d70b73aSGreg Kroah-Hartman 
31990ab5ee9SRusty Russell static bool debug;
3202d70b73aSGreg Kroah-Hartman module_param(debug, bool, S_IRUGO | S_IWUSR);
3212d70b73aSGreg Kroah-Hartman MODULE_PARM_DESC(debug, "Debug enabled or not");
3222d70b73aSGreg Kroah-Hartman 
3237e960711SCorentin Chary static int sabi_command(struct samsung_laptop *samsung, u16 command,
3247e960711SCorentin Chary 			struct sabi_data *in,
3257e960711SCorentin Chary 			struct sabi_data *out)
3262d70b73aSGreg Kroah-Hartman {
327a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
3287e960711SCorentin Chary 	int ret = 0;
329a6df4894SCorentin Chary 	u16 port = readw(samsung->sabi + config->header_offsets.port);
3302d70b73aSGreg Kroah-Hartman 	u8 complete, iface_data;
3312d70b73aSGreg Kroah-Hartman 
332a6df4894SCorentin Chary 	mutex_lock(&samsung->sabi_mutex);
3332d70b73aSGreg Kroah-Hartman 
3347e960711SCorentin Chary 	if (debug) {
3357e960711SCorentin Chary 		if (in)
3367e960711SCorentin Chary 			pr_info("SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
3377e960711SCorentin Chary 				command, in->d0, in->d1, in->d2, in->d3);
3387e960711SCorentin Chary 		else
3397e960711SCorentin Chary 			pr_info("SABI 0x%04x", command);
3407e960711SCorentin Chary 	}
3417e960711SCorentin Chary 
3422d70b73aSGreg Kroah-Hartman 	/* enable memory to be able to write to it */
343a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
3442d70b73aSGreg Kroah-Hartman 
3452d70b73aSGreg Kroah-Hartman 	/* write out the command */
346a6df4894SCorentin Chary 	writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
347a6df4894SCorentin Chary 	writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
348a6df4894SCorentin Chary 	writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
3497e960711SCorentin Chary 	if (in) {
3507e960711SCorentin Chary 		writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
3517e960711SCorentin Chary 		writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
3527e960711SCorentin Chary 		writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
3537e960711SCorentin Chary 		writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
3547e960711SCorentin Chary 	}
355a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
3562d70b73aSGreg Kroah-Hartman 
3572d70b73aSGreg Kroah-Hartman 	/* write protect memory to make it safe */
358a6df4894SCorentin Chary 	outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
3592d70b73aSGreg Kroah-Hartman 
3602d70b73aSGreg Kroah-Hartman 	/* see if the command actually succeeded */
361a6df4894SCorentin Chary 	complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
362a6df4894SCorentin Chary 	iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
3632d70b73aSGreg Kroah-Hartman 	if (complete != 0xaa || iface_data == 0xff) {
3647e960711SCorentin Chary 		pr_warn("SABI command 0x%04x failed with"
3657e960711SCorentin Chary 			" completion flag 0x%02x and interface data 0x%02x",
3662d70b73aSGreg Kroah-Hartman 			command, complete, iface_data);
3677e960711SCorentin Chary 		ret = -EINVAL;
3682d70b73aSGreg Kroah-Hartman 		goto exit;
3692d70b73aSGreg Kroah-Hartman 	}
3707e960711SCorentin Chary 
3717e960711SCorentin Chary 	if (out) {
3727e960711SCorentin Chary 		out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
3737e960711SCorentin Chary 		out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
3747e960711SCorentin Chary 		out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
3757e960711SCorentin Chary 		out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
3767e960711SCorentin Chary 	}
3777e960711SCorentin Chary 
3787e960711SCorentin Chary 	if (debug && out) {
3797e960711SCorentin Chary 		pr_info("SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}",
3807e960711SCorentin Chary 			out->d0, out->d1, out->d2, out->d3);
3817e960711SCorentin Chary 	}
3822d70b73aSGreg Kroah-Hartman 
3832d70b73aSGreg Kroah-Hartman exit:
384a6df4894SCorentin Chary 	mutex_unlock(&samsung->sabi_mutex);
3857e960711SCorentin Chary 	return ret;
3862d70b73aSGreg Kroah-Hartman }
3872d70b73aSGreg Kroah-Hartman 
3887e960711SCorentin Chary /* simple wrappers usable with most commands */
3897e960711SCorentin Chary static int sabi_set_commandb(struct samsung_laptop *samsung,
3907e960711SCorentin Chary 			     u16 command, u8 data)
3912d70b73aSGreg Kroah-Hartman {
3927e960711SCorentin Chary 	struct sabi_data in = { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 };
3932d70b73aSGreg Kroah-Hartman 
3947e960711SCorentin Chary 	in.data[0] = data;
3957e960711SCorentin Chary 	return sabi_command(samsung, command, &in, NULL);
3962d70b73aSGreg Kroah-Hartman }
3972d70b73aSGreg Kroah-Hartman 
3985dea7a20SCorentin Chary static int read_brightness(struct samsung_laptop *samsung)
3992d70b73aSGreg Kroah-Hartman {
400a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
401a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
4027e960711SCorentin Chary 	struct sabi_data sretval;
4032d70b73aSGreg Kroah-Hartman 	int user_brightness = 0;
4042d70b73aSGreg Kroah-Hartman 	int retval;
4052d70b73aSGreg Kroah-Hartman 
4067e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_brightness,
4077e960711SCorentin Chary 			      NULL, &sretval);
4087e960711SCorentin Chary 	if (retval)
4097e960711SCorentin Chary 		return retval;
4107e960711SCorentin Chary 
4117e960711SCorentin Chary 	user_brightness = sretval.data[0];
412a6df4894SCorentin Chary 	if (user_brightness > config->min_brightness)
413a6df4894SCorentin Chary 		user_brightness -= config->min_brightness;
414bee460beSJason Stubbs 	else
415bee460beSJason Stubbs 		user_brightness = 0;
4167e960711SCorentin Chary 
4172d70b73aSGreg Kroah-Hartman 	return user_brightness;
4182d70b73aSGreg Kroah-Hartman }
4192d70b73aSGreg Kroah-Hartman 
4205dea7a20SCorentin Chary static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
4212d70b73aSGreg Kroah-Hartman {
422a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
423a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
424a6df4894SCorentin Chary 	u8 user_level = user_brightness + config->min_brightness;
4252d70b73aSGreg Kroah-Hartman 
426a6df4894SCorentin Chary 	if (samsung->has_stepping_quirk && user_level != 0) {
427ac080523SJason Stubbs 		/*
428ac080523SJason Stubbs 		 * short circuit if the specified level is what's already set
429ac080523SJason Stubbs 		 * to prevent the screen from flickering needlessly
430ac080523SJason Stubbs 		 */
4315dea7a20SCorentin Chary 		if (user_brightness == read_brightness(samsung))
432ac080523SJason Stubbs 			return;
433ac080523SJason Stubbs 
4347e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_brightness, 0);
435ac080523SJason Stubbs 	}
436ac080523SJason Stubbs 
4377e960711SCorentin Chary 	sabi_set_commandb(samsung, commands->set_brightness, user_level);
4382d70b73aSGreg Kroah-Hartman }
4392d70b73aSGreg Kroah-Hartman 
4402d70b73aSGreg Kroah-Hartman static int get_brightness(struct backlight_device *bd)
4412d70b73aSGreg Kroah-Hartman {
4425dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
4435dea7a20SCorentin Chary 
4445dea7a20SCorentin Chary 	return read_brightness(samsung);
4452d70b73aSGreg Kroah-Hartman }
4462d70b73aSGreg Kroah-Hartman 
4475dea7a20SCorentin Chary static void check_for_stepping_quirk(struct samsung_laptop *samsung)
448ac080523SJason Stubbs {
4495dea7a20SCorentin Chary 	int initial_level;
4505dea7a20SCorentin Chary 	int check_level;
4515dea7a20SCorentin Chary 	int orig_level = read_brightness(samsung);
452ac080523SJason Stubbs 
453ac080523SJason Stubbs 	/*
454ac080523SJason Stubbs 	 * Some laptops exhibit the strange behaviour of stepping toward
455ac080523SJason Stubbs 	 * (rather than setting) the brightness except when changing to/from
456ac080523SJason Stubbs 	 * brightness level 0. This behaviour is checked for here and worked
457ac080523SJason Stubbs 	 * around in set_brightness.
458ac080523SJason Stubbs 	 */
459ac080523SJason Stubbs 
460ba05b237SJohn Serock 	if (orig_level == 0)
4615dea7a20SCorentin Chary 		set_brightness(samsung, 1);
462ba05b237SJohn Serock 
4635dea7a20SCorentin Chary 	initial_level = read_brightness(samsung);
464ba05b237SJohn Serock 
465ac080523SJason Stubbs 	if (initial_level <= 2)
466ac080523SJason Stubbs 		check_level = initial_level + 2;
467ac080523SJason Stubbs 	else
468ac080523SJason Stubbs 		check_level = initial_level - 2;
469ac080523SJason Stubbs 
470a6df4894SCorentin Chary 	samsung->has_stepping_quirk = false;
4715dea7a20SCorentin Chary 	set_brightness(samsung, check_level);
472ac080523SJason Stubbs 
4735dea7a20SCorentin Chary 	if (read_brightness(samsung) != check_level) {
474a6df4894SCorentin Chary 		samsung->has_stepping_quirk = true;
475ac080523SJason Stubbs 		pr_info("enabled workaround for brightness stepping quirk\n");
476ac080523SJason Stubbs 	}
477ac080523SJason Stubbs 
4785dea7a20SCorentin Chary 	set_brightness(samsung, orig_level);
479ac080523SJason Stubbs }
480ac080523SJason Stubbs 
4812d70b73aSGreg Kroah-Hartman static int update_status(struct backlight_device *bd)
4822d70b73aSGreg Kroah-Hartman {
4835dea7a20SCorentin Chary 	struct samsung_laptop *samsung = bl_get_data(bd);
484a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
485a6df4894SCorentin Chary 
4865dea7a20SCorentin Chary 	set_brightness(samsung, bd->props.brightness);
4872d70b73aSGreg Kroah-Hartman 
4882d70b73aSGreg Kroah-Hartman 	if (bd->props.power == FB_BLANK_UNBLANK)
4897e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 1);
4902d70b73aSGreg Kroah-Hartman 	else
4917e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_backlight, 0);
4925dea7a20SCorentin Chary 
4932d70b73aSGreg Kroah-Hartman 	return 0;
4942d70b73aSGreg Kroah-Hartman }
4952d70b73aSGreg Kroah-Hartman 
4962d70b73aSGreg Kroah-Hartman static const struct backlight_ops backlight_ops = {
4972d70b73aSGreg Kroah-Hartman 	.get_brightness	= get_brightness,
4982d70b73aSGreg Kroah-Hartman 	.update_status	= update_status,
4992d70b73aSGreg Kroah-Hartman };
5002d70b73aSGreg Kroah-Hartman 
5012d70b73aSGreg Kroah-Hartman static int rfkill_set(void *data, bool blocked)
5022d70b73aSGreg Kroah-Hartman {
5035dea7a20SCorentin Chary 	struct samsung_laptop *samsung = data;
504a6df4894SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
505a6df4894SCorentin Chary 
5062d70b73aSGreg Kroah-Hartman 	/* Do something with blocked...*/
5072d70b73aSGreg Kroah-Hartman 	/*
5082d70b73aSGreg Kroah-Hartman 	 * blocked == false is on
5092d70b73aSGreg Kroah-Hartman 	 * blocked == true is off
5102d70b73aSGreg Kroah-Hartman 	 */
5112d70b73aSGreg Kroah-Hartman 	if (blocked)
5127e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_wireless_button, 0);
5132d70b73aSGreg Kroah-Hartman 	else
5147e960711SCorentin Chary 		sabi_set_commandb(samsung, commands->set_wireless_button, 1);
5152d70b73aSGreg Kroah-Hartman 
5162d70b73aSGreg Kroah-Hartman 	return 0;
5172d70b73aSGreg Kroah-Hartman }
5182d70b73aSGreg Kroah-Hartman 
5192d70b73aSGreg Kroah-Hartman static struct rfkill_ops rfkill_ops = {
5202d70b73aSGreg Kroah-Hartman 	.set_block = rfkill_set,
5212d70b73aSGreg Kroah-Hartman };
5222d70b73aSGreg Kroah-Hartman 
5232d70b73aSGreg Kroah-Hartman static ssize_t get_performance_level(struct device *dev,
5242d70b73aSGreg Kroah-Hartman 				     struct device_attribute *attr, char *buf)
5252d70b73aSGreg Kroah-Hartman {
5265dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
527a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
5285dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
5297e960711SCorentin Chary 	struct sabi_data sretval;
5302d70b73aSGreg Kroah-Hartman 	int retval;
5312d70b73aSGreg Kroah-Hartman 	int i;
5322d70b73aSGreg Kroah-Hartman 
5332d70b73aSGreg Kroah-Hartman 	/* Read the state */
5347e960711SCorentin Chary 	retval = sabi_command(samsung, commands->get_performance_level,
5357e960711SCorentin Chary 			      NULL, &sretval);
5362d70b73aSGreg Kroah-Hartman 	if (retval)
5372d70b73aSGreg Kroah-Hartman 		return retval;
5382d70b73aSGreg Kroah-Hartman 
5392d70b73aSGreg Kroah-Hartman 	/* The logic is backwards, yeah, lots of fun... */
540a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
5417e960711SCorentin Chary 		if (sretval.data[0] == config->performance_levels[i].value)
542a6df4894SCorentin Chary 			return sprintf(buf, "%s\n", config->performance_levels[i].name);
5432d70b73aSGreg Kroah-Hartman 	}
5442d70b73aSGreg Kroah-Hartman 	return sprintf(buf, "%s\n", "unknown");
5452d70b73aSGreg Kroah-Hartman }
5462d70b73aSGreg Kroah-Hartman 
5472d70b73aSGreg Kroah-Hartman static ssize_t set_performance_level(struct device *dev,
5482d70b73aSGreg Kroah-Hartman 				struct device_attribute *attr, const char *buf,
5492d70b73aSGreg Kroah-Hartman 				size_t count)
5502d70b73aSGreg Kroah-Hartman {
5515dea7a20SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
552a6df4894SCorentin Chary 	const struct sabi_config *config = samsung->config;
5535dea7a20SCorentin Chary 	const struct sabi_commands *commands = &config->commands;
5542d70b73aSGreg Kroah-Hartman 	int i;
5555dea7a20SCorentin Chary 
5565dea7a20SCorentin Chary 	if (count < 1)
5575dea7a20SCorentin Chary 		return count;
5585dea7a20SCorentin Chary 
559a6df4894SCorentin Chary 	for (i = 0; config->performance_levels[i].name; ++i) {
5602d70b73aSGreg Kroah-Hartman 		const struct sabi_performance_level *level =
561a6df4894SCorentin Chary 			&config->performance_levels[i];
5622d70b73aSGreg Kroah-Hartman 		if (!strncasecmp(level->name, buf, strlen(level->name))) {
5637e960711SCorentin Chary 			sabi_set_commandb(samsung,
5645dea7a20SCorentin Chary 					  commands->set_performance_level,
5652d70b73aSGreg Kroah-Hartman 					  level->value);
5662d70b73aSGreg Kroah-Hartman 			break;
5672d70b73aSGreg Kroah-Hartman 		}
5682d70b73aSGreg Kroah-Hartman 	}
5695dea7a20SCorentin Chary 
570a6df4894SCorentin Chary 	if (!config->performance_levels[i].name)
5712d70b73aSGreg Kroah-Hartman 		return -EINVAL;
5725dea7a20SCorentin Chary 
5732d70b73aSGreg Kroah-Hartman 	return count;
5742d70b73aSGreg Kroah-Hartman }
5755dea7a20SCorentin Chary 
5762d70b73aSGreg Kroah-Hartman static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
5772d70b73aSGreg Kroah-Hartman 		   get_performance_level, set_performance_level);
5782d70b73aSGreg Kroah-Hartman 
579cb5b5c91SCorentin Chary static int read_battery_life_extender(struct samsung_laptop *samsung)
580cb5b5c91SCorentin Chary {
581cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
582cb5b5c91SCorentin Chary 	struct sabi_data data;
583cb5b5c91SCorentin Chary 	int retval;
584cb5b5c91SCorentin Chary 
585cb5b5c91SCorentin Chary 	if (commands->get_battery_life_extender == 0xFFFF)
586cb5b5c91SCorentin Chary 		return -ENODEV;
587cb5b5c91SCorentin Chary 
588cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
589cb5b5c91SCorentin Chary 	data.data[0] = 0x80;
590cb5b5c91SCorentin Chary 	retval = sabi_command(samsung, commands->get_battery_life_extender,
591cb5b5c91SCorentin Chary 			      &data, &data);
592cb5b5c91SCorentin Chary 
593cb5b5c91SCorentin Chary 	if (retval)
594cb5b5c91SCorentin Chary 		return retval;
595cb5b5c91SCorentin Chary 
596cb5b5c91SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
597cb5b5c91SCorentin Chary 		return -ENODEV;
598cb5b5c91SCorentin Chary 
599cb5b5c91SCorentin Chary 	return data.data[0];
600cb5b5c91SCorentin Chary }
601cb5b5c91SCorentin Chary 
602cb5b5c91SCorentin Chary static int write_battery_life_extender(struct samsung_laptop *samsung,
603cb5b5c91SCorentin Chary 				       int enabled)
604cb5b5c91SCorentin Chary {
605cb5b5c91SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
606cb5b5c91SCorentin Chary 	struct sabi_data data;
607cb5b5c91SCorentin Chary 
608cb5b5c91SCorentin Chary 	memset(&data, 0, sizeof(data));
609cb5b5c91SCorentin Chary 	data.data[0] = 0x80 | enabled;
610cb5b5c91SCorentin Chary 	return sabi_command(samsung, commands->set_battery_life_extender,
611cb5b5c91SCorentin Chary 			    &data, NULL);
612cb5b5c91SCorentin Chary }
613cb5b5c91SCorentin Chary 
614cb5b5c91SCorentin Chary static ssize_t get_battery_life_extender(struct device *dev,
615cb5b5c91SCorentin Chary 					 struct device_attribute *attr,
616cb5b5c91SCorentin Chary 					 char *buf)
617cb5b5c91SCorentin Chary {
618cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
619cb5b5c91SCorentin Chary 	int ret;
620cb5b5c91SCorentin Chary 
621cb5b5c91SCorentin Chary 	ret = read_battery_life_extender(samsung);
622cb5b5c91SCorentin Chary 	if (ret < 0)
623cb5b5c91SCorentin Chary 		return ret;
624cb5b5c91SCorentin Chary 
625cb5b5c91SCorentin Chary 	return sprintf(buf, "%d\n", ret);
626cb5b5c91SCorentin Chary }
627cb5b5c91SCorentin Chary 
628cb5b5c91SCorentin Chary static ssize_t set_battery_life_extender(struct device *dev,
629cb5b5c91SCorentin Chary 					struct device_attribute *attr,
630cb5b5c91SCorentin Chary 					const char *buf, size_t count)
631cb5b5c91SCorentin Chary {
632cb5b5c91SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
633cb5b5c91SCorentin Chary 	int ret, value;
634cb5b5c91SCorentin Chary 
635cb5b5c91SCorentin Chary 	if (!count || sscanf(buf, "%i", &value) != 1)
636cb5b5c91SCorentin Chary 		return -EINVAL;
637cb5b5c91SCorentin Chary 
638cb5b5c91SCorentin Chary 	ret = write_battery_life_extender(samsung, !!value);
639cb5b5c91SCorentin Chary 	if (ret < 0)
640cb5b5c91SCorentin Chary 		return ret;
641cb5b5c91SCorentin Chary 
642cb5b5c91SCorentin Chary 	return count;
643cb5b5c91SCorentin Chary }
644cb5b5c91SCorentin Chary 
645cb5b5c91SCorentin Chary static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
646cb5b5c91SCorentin Chary 		   get_battery_life_extender, set_battery_life_extender);
647cb5b5c91SCorentin Chary 
6483a75d378SCorentin Chary static int read_usb_charge(struct samsung_laptop *samsung)
6493a75d378SCorentin Chary {
6503a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
6513a75d378SCorentin Chary 	struct sabi_data data;
6523a75d378SCorentin Chary 	int retval;
6533a75d378SCorentin Chary 
6543a75d378SCorentin Chary 	if (commands->get_usb_charge == 0xFFFF)
6553a75d378SCorentin Chary 		return -ENODEV;
6563a75d378SCorentin Chary 
6573a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
6583a75d378SCorentin Chary 	data.data[0] = 0x80;
6593a75d378SCorentin Chary 	retval = sabi_command(samsung, commands->get_usb_charge,
6603a75d378SCorentin Chary 			      &data, &data);
6613a75d378SCorentin Chary 
6623a75d378SCorentin Chary 	if (retval)
6633a75d378SCorentin Chary 		return retval;
6643a75d378SCorentin Chary 
6653a75d378SCorentin Chary 	if (data.data[0] != 0 && data.data[0] != 1)
6663a75d378SCorentin Chary 		return -ENODEV;
6673a75d378SCorentin Chary 
6683a75d378SCorentin Chary 	return data.data[0];
6693a75d378SCorentin Chary }
6703a75d378SCorentin Chary 
6713a75d378SCorentin Chary static int write_usb_charge(struct samsung_laptop *samsung,
6723a75d378SCorentin Chary 			    int enabled)
6733a75d378SCorentin Chary {
6743a75d378SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
6753a75d378SCorentin Chary 	struct sabi_data data;
6763a75d378SCorentin Chary 
6773a75d378SCorentin Chary 	memset(&data, 0, sizeof(data));
6783a75d378SCorentin Chary 	data.data[0] = 0x80 | enabled;
6793a75d378SCorentin Chary 	return sabi_command(samsung, commands->set_usb_charge,
6803a75d378SCorentin Chary 			    &data, NULL);
6813a75d378SCorentin Chary }
6823a75d378SCorentin Chary 
6833a75d378SCorentin Chary static ssize_t get_usb_charge(struct device *dev,
6843a75d378SCorentin Chary 			      struct device_attribute *attr,
6853a75d378SCorentin Chary 			      char *buf)
6863a75d378SCorentin Chary {
6873a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
6883a75d378SCorentin Chary 	int ret;
6893a75d378SCorentin Chary 
6903a75d378SCorentin Chary 	ret = read_usb_charge(samsung);
6913a75d378SCorentin Chary 	if (ret < 0)
6923a75d378SCorentin Chary 		return ret;
6933a75d378SCorentin Chary 
6943a75d378SCorentin Chary 	return sprintf(buf, "%d\n", ret);
6953a75d378SCorentin Chary }
6963a75d378SCorentin Chary 
6973a75d378SCorentin Chary static ssize_t set_usb_charge(struct device *dev,
6983a75d378SCorentin Chary 			      struct device_attribute *attr,
6993a75d378SCorentin Chary 			      const char *buf, size_t count)
7003a75d378SCorentin Chary {
7013a75d378SCorentin Chary 	struct samsung_laptop *samsung = dev_get_drvdata(dev);
7023a75d378SCorentin Chary 	int ret, value;
7033a75d378SCorentin Chary 
7043a75d378SCorentin Chary 	if (!count || sscanf(buf, "%i", &value) != 1)
7053a75d378SCorentin Chary 		return -EINVAL;
7063a75d378SCorentin Chary 
7073a75d378SCorentin Chary 	ret = write_usb_charge(samsung, !!value);
7083a75d378SCorentin Chary 	if (ret < 0)
7093a75d378SCorentin Chary 		return ret;
7103a75d378SCorentin Chary 
7113a75d378SCorentin Chary 	return count;
7123a75d378SCorentin Chary }
7133a75d378SCorentin Chary 
7143a75d378SCorentin Chary static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
7153a75d378SCorentin Chary 		   get_usb_charge, set_usb_charge);
7163a75d378SCorentin Chary 
717a66c1662SCorentin Chary static struct attribute *platform_attributes[] = {
718a66c1662SCorentin Chary 	&dev_attr_performance_level.attr,
719cb5b5c91SCorentin Chary 	&dev_attr_battery_life_extender.attr,
7203a75d378SCorentin Chary 	&dev_attr_usb_charge.attr,
721a66c1662SCorentin Chary 	NULL
722a66c1662SCorentin Chary };
7232d70b73aSGreg Kroah-Hartman 
7245dea7a20SCorentin Chary static int find_signature(void __iomem *memcheck, const char *testStr)
7255dea7a20SCorentin Chary {
7265dea7a20SCorentin Chary 	int i = 0;
7275dea7a20SCorentin Chary 	int loca;
7285dea7a20SCorentin Chary 
7295dea7a20SCorentin Chary 	for (loca = 0; loca < 0xffff; loca++) {
7305dea7a20SCorentin Chary 		char temp = readb(memcheck + loca);
7315dea7a20SCorentin Chary 
7325dea7a20SCorentin Chary 		if (temp == testStr[i]) {
7335dea7a20SCorentin Chary 			if (i == strlen(testStr)-1)
7345dea7a20SCorentin Chary 				break;
7355dea7a20SCorentin Chary 			++i;
7365dea7a20SCorentin Chary 		} else {
7375dea7a20SCorentin Chary 			i = 0;
7385dea7a20SCorentin Chary 		}
7395dea7a20SCorentin Chary 	}
7405dea7a20SCorentin Chary 	return loca;
7415dea7a20SCorentin Chary }
7425dea7a20SCorentin Chary 
7435dea7a20SCorentin Chary static void samsung_rfkill_exit(struct samsung_laptop *samsung)
7445dea7a20SCorentin Chary {
7455dea7a20SCorentin Chary 	if (samsung->rfk) {
7465dea7a20SCorentin Chary 		rfkill_unregister(samsung->rfk);
7475dea7a20SCorentin Chary 		rfkill_destroy(samsung->rfk);
7485dea7a20SCorentin Chary 		samsung->rfk = NULL;
7495dea7a20SCorentin Chary 	}
7505dea7a20SCorentin Chary }
7515dea7a20SCorentin Chary 
7525dea7a20SCorentin Chary static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
7535dea7a20SCorentin Chary {
7545dea7a20SCorentin Chary 	int retval;
7555dea7a20SCorentin Chary 
7565dea7a20SCorentin Chary 	samsung->rfk = rfkill_alloc("samsung-wifi",
7575dea7a20SCorentin Chary 				    &samsung->platform_device->dev,
7585dea7a20SCorentin Chary 				    RFKILL_TYPE_WLAN,
7595dea7a20SCorentin Chary 				    &rfkill_ops, samsung);
7605dea7a20SCorentin Chary 	if (!samsung->rfk)
7615dea7a20SCorentin Chary 		return -ENOMEM;
7625dea7a20SCorentin Chary 
7635dea7a20SCorentin Chary 	retval = rfkill_register(samsung->rfk);
7645dea7a20SCorentin Chary 	if (retval) {
7655dea7a20SCorentin Chary 		rfkill_destroy(samsung->rfk);
7665dea7a20SCorentin Chary 		samsung->rfk = NULL;
7675dea7a20SCorentin Chary 		return -ENODEV;
7685dea7a20SCorentin Chary 	}
7695dea7a20SCorentin Chary 
7705dea7a20SCorentin Chary 	return 0;
7715dea7a20SCorentin Chary }
7725dea7a20SCorentin Chary 
773f674ebf1SCorentin Chary static int kbd_backlight_enable(struct samsung_laptop *samsung)
774f674ebf1SCorentin Chary {
775f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
776f674ebf1SCorentin Chary 	struct sabi_data data;
777f674ebf1SCorentin Chary 	int retval;
778f674ebf1SCorentin Chary 
779f674ebf1SCorentin Chary 	if (commands->kbd_backlight == 0xFFFF)
780f674ebf1SCorentin Chary 		return -ENODEV;
781f674ebf1SCorentin Chary 
782f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
783f674ebf1SCorentin Chary 	data.d0 = 0xaabb;
784f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
785f674ebf1SCorentin Chary 			      &data, &data);
786f674ebf1SCorentin Chary 
787f674ebf1SCorentin Chary 	if (retval)
788f674ebf1SCorentin Chary 		return retval;
789f674ebf1SCorentin Chary 
790f674ebf1SCorentin Chary 	if (data.d0 != 0xccdd)
791f674ebf1SCorentin Chary 		return -ENODEV;
792f674ebf1SCorentin Chary 	return 0;
793f674ebf1SCorentin Chary }
794f674ebf1SCorentin Chary 
795f674ebf1SCorentin Chary static int kbd_backlight_read(struct samsung_laptop *samsung)
796f674ebf1SCorentin Chary {
797f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
798f674ebf1SCorentin Chary 	struct sabi_data data;
799f674ebf1SCorentin Chary 	int retval;
800f674ebf1SCorentin Chary 
801f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
802f674ebf1SCorentin Chary 	data.data[0] = 0x81;
803f674ebf1SCorentin Chary 	retval = sabi_command(samsung, commands->kbd_backlight,
804f674ebf1SCorentin Chary 			      &data, &data);
805f674ebf1SCorentin Chary 
806f674ebf1SCorentin Chary 	if (retval)
807f674ebf1SCorentin Chary 		return retval;
808f674ebf1SCorentin Chary 
809f674ebf1SCorentin Chary 	return data.data[0];
810f674ebf1SCorentin Chary }
811f674ebf1SCorentin Chary 
812f674ebf1SCorentin Chary static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
813f674ebf1SCorentin Chary {
814f674ebf1SCorentin Chary 	const struct sabi_commands *commands = &samsung->config->commands;
815f674ebf1SCorentin Chary 	struct sabi_data data;
816f674ebf1SCorentin Chary 
817f674ebf1SCorentin Chary 	memset(&data, 0, sizeof(data));
818f674ebf1SCorentin Chary 	data.d0 = 0x82 | ((brightness & 0xFF) << 8);
819f674ebf1SCorentin Chary 	return sabi_command(samsung, commands->kbd_backlight,
820f674ebf1SCorentin Chary 			    &data, NULL);
821f674ebf1SCorentin Chary }
822f674ebf1SCorentin Chary 
823f674ebf1SCorentin Chary static void kbd_led_update(struct work_struct *work)
824f674ebf1SCorentin Chary {
825f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
826f674ebf1SCorentin Chary 
827f674ebf1SCorentin Chary 	samsung = container_of(work, struct samsung_laptop, kbd_led_work);
828f674ebf1SCorentin Chary 	kbd_backlight_write(samsung, samsung->kbd_led_wk);
829f674ebf1SCorentin Chary }
830f674ebf1SCorentin Chary 
831f674ebf1SCorentin Chary static void kbd_led_set(struct led_classdev *led_cdev,
832f674ebf1SCorentin Chary 			enum led_brightness value)
833f674ebf1SCorentin Chary {
834f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
835f674ebf1SCorentin Chary 
836f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
837f674ebf1SCorentin Chary 
838f674ebf1SCorentin Chary 	if (value > samsung->kbd_led.max_brightness)
839f674ebf1SCorentin Chary 		value = samsung->kbd_led.max_brightness;
840f674ebf1SCorentin Chary 	else if (value < 0)
841f674ebf1SCorentin Chary 		value = 0;
842f674ebf1SCorentin Chary 
843f674ebf1SCorentin Chary 	samsung->kbd_led_wk = value;
844f674ebf1SCorentin Chary 	queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
845f674ebf1SCorentin Chary }
846f674ebf1SCorentin Chary 
847f674ebf1SCorentin Chary static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
848f674ebf1SCorentin Chary {
849f674ebf1SCorentin Chary 	struct samsung_laptop *samsung;
850f674ebf1SCorentin Chary 
851f674ebf1SCorentin Chary 	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
852f674ebf1SCorentin Chary 	return kbd_backlight_read(samsung);
853f674ebf1SCorentin Chary }
854f674ebf1SCorentin Chary 
855f674ebf1SCorentin Chary static void samsung_leds_exit(struct samsung_laptop *samsung)
856f674ebf1SCorentin Chary {
857f674ebf1SCorentin Chary 	if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
858f674ebf1SCorentin Chary 		led_classdev_unregister(&samsung->kbd_led);
859f674ebf1SCorentin Chary 	if (samsung->led_workqueue)
860f674ebf1SCorentin Chary 		destroy_workqueue(samsung->led_workqueue);
861f674ebf1SCorentin Chary }
862f674ebf1SCorentin Chary 
863f674ebf1SCorentin Chary static int __init samsung_leds_init(struct samsung_laptop *samsung)
864f674ebf1SCorentin Chary {
865f674ebf1SCorentin Chary 	int ret = 0;
866f674ebf1SCorentin Chary 
867f674ebf1SCorentin Chary 	samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
868f674ebf1SCorentin Chary 	if (!samsung->led_workqueue)
869f674ebf1SCorentin Chary 		return -ENOMEM;
870f674ebf1SCorentin Chary 
871f674ebf1SCorentin Chary 	if (kbd_backlight_enable(samsung) >= 0) {
872f674ebf1SCorentin Chary 		INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
873f674ebf1SCorentin Chary 
874f674ebf1SCorentin Chary 		samsung->kbd_led.name = "samsung::kbd_backlight";
875f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_set = kbd_led_set;
876f674ebf1SCorentin Chary 		samsung->kbd_led.brightness_get = kbd_led_get;
877f674ebf1SCorentin Chary 		samsung->kbd_led.max_brightness = 8;
878f674ebf1SCorentin Chary 
879f674ebf1SCorentin Chary 		ret = led_classdev_register(&samsung->platform_device->dev,
880f674ebf1SCorentin Chary 					   &samsung->kbd_led);
881f674ebf1SCorentin Chary 	}
882f674ebf1SCorentin Chary 
883f674ebf1SCorentin Chary 	if (ret)
884f674ebf1SCorentin Chary 		samsung_leds_exit(samsung);
885f674ebf1SCorentin Chary 
886f674ebf1SCorentin Chary 	return ret;
887f674ebf1SCorentin Chary }
888f674ebf1SCorentin Chary 
8895dea7a20SCorentin Chary static void samsung_backlight_exit(struct samsung_laptop *samsung)
8905dea7a20SCorentin Chary {
8915dea7a20SCorentin Chary 	if (samsung->backlight_device) {
8925dea7a20SCorentin Chary 		backlight_device_unregister(samsung->backlight_device);
8935dea7a20SCorentin Chary 		samsung->backlight_device = NULL;
8945dea7a20SCorentin Chary 	}
8955dea7a20SCorentin Chary }
8965dea7a20SCorentin Chary 
8975dea7a20SCorentin Chary static int __init samsung_backlight_init(struct samsung_laptop *samsung)
8985dea7a20SCorentin Chary {
8995dea7a20SCorentin Chary 	struct backlight_device *bd;
9005dea7a20SCorentin Chary 	struct backlight_properties props;
9015dea7a20SCorentin Chary 
902f34cd9caSCorentin Chary 	if (!samsung->handle_backlight)
903f34cd9caSCorentin Chary 		return 0;
904f34cd9caSCorentin Chary 
9055dea7a20SCorentin Chary 	memset(&props, 0, sizeof(struct backlight_properties));
9065dea7a20SCorentin Chary 	props.type = BACKLIGHT_PLATFORM;
9075dea7a20SCorentin Chary 	props.max_brightness = samsung->config->max_brightness -
9085dea7a20SCorentin Chary 		samsung->config->min_brightness;
9095dea7a20SCorentin Chary 
9105dea7a20SCorentin Chary 	bd = backlight_device_register("samsung",
9115dea7a20SCorentin Chary 				       &samsung->platform_device->dev,
9125dea7a20SCorentin Chary 				       samsung, &backlight_ops,
9135dea7a20SCorentin Chary 				       &props);
9145dea7a20SCorentin Chary 	if (IS_ERR(bd))
9155dea7a20SCorentin Chary 		return PTR_ERR(bd);
9165dea7a20SCorentin Chary 
9175dea7a20SCorentin Chary 	samsung->backlight_device = bd;
9185dea7a20SCorentin Chary 	samsung->backlight_device->props.brightness = read_brightness(samsung);
9195dea7a20SCorentin Chary 	samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
9205dea7a20SCorentin Chary 	backlight_update_status(samsung->backlight_device);
9215dea7a20SCorentin Chary 
9225dea7a20SCorentin Chary 	return 0;
9235dea7a20SCorentin Chary }
9245dea7a20SCorentin Chary 
925a66c1662SCorentin Chary static mode_t samsung_sysfs_is_visible(struct kobject *kobj,
926a66c1662SCorentin Chary 				       struct attribute *attr, int idx)
927a66c1662SCorentin Chary {
928a66c1662SCorentin Chary 	struct device *dev = container_of(kobj, struct device, kobj);
929a66c1662SCorentin Chary 	struct platform_device *pdev = to_platform_device(dev);
930a66c1662SCorentin Chary 	struct samsung_laptop *samsung = platform_get_drvdata(pdev);
931a66c1662SCorentin Chary 	bool ok = true;
932a66c1662SCorentin Chary 
933a66c1662SCorentin Chary 	if (attr == &dev_attr_performance_level.attr)
934a66c1662SCorentin Chary 		ok = !!samsung->config->performance_levels[0].name;
935cb5b5c91SCorentin Chary 	if (attr == &dev_attr_battery_life_extender.attr)
936cb5b5c91SCorentin Chary 		ok = !!(read_battery_life_extender(samsung) >= 0);
9373a75d378SCorentin Chary 	if (attr == &dev_attr_usb_charge.attr)
9383a75d378SCorentin Chary 		ok = !!(read_usb_charge(samsung) >= 0);
939a66c1662SCorentin Chary 
940a66c1662SCorentin Chary 	return ok ? attr->mode : 0;
941a66c1662SCorentin Chary }
942a66c1662SCorentin Chary 
943a66c1662SCorentin Chary static struct attribute_group platform_attribute_group = {
944a66c1662SCorentin Chary 	.is_visible = samsung_sysfs_is_visible,
945a66c1662SCorentin Chary 	.attrs = platform_attributes
946a66c1662SCorentin Chary };
947a66c1662SCorentin Chary 
9485dea7a20SCorentin Chary static void samsung_sysfs_exit(struct samsung_laptop *samsung)
9495dea7a20SCorentin Chary {
950a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
951a66c1662SCorentin Chary 
952a66c1662SCorentin Chary 	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
9535dea7a20SCorentin Chary }
9545dea7a20SCorentin Chary 
9555dea7a20SCorentin Chary static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
9565dea7a20SCorentin Chary {
957a66c1662SCorentin Chary 	struct platform_device *device = samsung->platform_device;
958a66c1662SCorentin Chary 
959a66c1662SCorentin Chary 	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
960a66c1662SCorentin Chary 
9615dea7a20SCorentin Chary }
9625dea7a20SCorentin Chary 
9635b80fc40SCorentin Chary static int show_call(struct seq_file *m, void *data)
9645b80fc40SCorentin Chary {
9655b80fc40SCorentin Chary 	struct samsung_laptop *samsung = m->private;
9665b80fc40SCorentin Chary 	struct sabi_data *sdata = &samsung->debug.data;
9675b80fc40SCorentin Chary 	int ret;
9685b80fc40SCorentin Chary 
9695b80fc40SCorentin Chary 	seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
9705b80fc40SCorentin Chary 		   samsung->debug.command,
9715b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
9725b80fc40SCorentin Chary 
9735b80fc40SCorentin Chary 	ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
9745b80fc40SCorentin Chary 
9755b80fc40SCorentin Chary 	if (ret) {
9765b80fc40SCorentin Chary 		seq_printf(m, "SABI command 0x%04x failed\n",
9775b80fc40SCorentin Chary 			   samsung->debug.command);
9785b80fc40SCorentin Chary 		return ret;
9795b80fc40SCorentin Chary 	}
9805b80fc40SCorentin Chary 
9815b80fc40SCorentin Chary 	seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
9825b80fc40SCorentin Chary 		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
9835b80fc40SCorentin Chary 	return 0;
9845b80fc40SCorentin Chary }
9855b80fc40SCorentin Chary 
9865b80fc40SCorentin Chary static int samsung_debugfs_open(struct inode *inode, struct file *file)
9875b80fc40SCorentin Chary {
9885b80fc40SCorentin Chary 	return single_open(file, show_call, inode->i_private);
9895b80fc40SCorentin Chary }
9905b80fc40SCorentin Chary 
9915b80fc40SCorentin Chary static const struct file_operations samsung_laptop_call_io_ops = {
9925b80fc40SCorentin Chary 	.owner = THIS_MODULE,
9935b80fc40SCorentin Chary 	.open = samsung_debugfs_open,
9945b80fc40SCorentin Chary 	.read = seq_read,
9955b80fc40SCorentin Chary 	.llseek = seq_lseek,
9965b80fc40SCorentin Chary 	.release = single_release,
9975b80fc40SCorentin Chary };
9985b80fc40SCorentin Chary 
9995b80fc40SCorentin Chary static void samsung_debugfs_exit(struct samsung_laptop *samsung)
10005b80fc40SCorentin Chary {
10015b80fc40SCorentin Chary 	debugfs_remove_recursive(samsung->debug.root);
10025b80fc40SCorentin Chary }
10035b80fc40SCorentin Chary 
10045b80fc40SCorentin Chary static int samsung_debugfs_init(struct samsung_laptop *samsung)
10055b80fc40SCorentin Chary {
10065b80fc40SCorentin Chary 	struct dentry *dent;
10075b80fc40SCorentin Chary 
10085b80fc40SCorentin Chary 	samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
10095b80fc40SCorentin Chary 	if (!samsung->debug.root) {
10105b80fc40SCorentin Chary 		pr_err("failed to create debugfs directory");
10115b80fc40SCorentin Chary 		goto error_debugfs;
10125b80fc40SCorentin Chary 	}
10135b80fc40SCorentin Chary 
10145b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
10155b80fc40SCorentin Chary 	samsung->debug.f0000_wrapper.size = 0xffff;
10165b80fc40SCorentin Chary 
10175b80fc40SCorentin Chary 	samsung->debug.data_wrapper.data = &samsung->debug.data;
10185b80fc40SCorentin Chary 	samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
10195b80fc40SCorentin Chary 
10205b80fc40SCorentin Chary 	dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
10215b80fc40SCorentin Chary 				  samsung->debug.root, &samsung->debug.command);
10225b80fc40SCorentin Chary 	if (!dent)
10235b80fc40SCorentin Chary 		goto error_debugfs;
10245b80fc40SCorentin Chary 
10255b80fc40SCorentin Chary 	dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
10265b80fc40SCorentin Chary 				  &samsung->debug.data.d0);
10275b80fc40SCorentin Chary 	if (!dent)
10285b80fc40SCorentin Chary 		goto error_debugfs;
10295b80fc40SCorentin Chary 
10305b80fc40SCorentin Chary 	dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
10315b80fc40SCorentin Chary 				  &samsung->debug.data.d1);
10325b80fc40SCorentin Chary 	if (!dent)
10335b80fc40SCorentin Chary 		goto error_debugfs;
10345b80fc40SCorentin Chary 
10355b80fc40SCorentin Chary 	dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
10365b80fc40SCorentin Chary 				  &samsung->debug.data.d2);
10375b80fc40SCorentin Chary 	if (!dent)
10385b80fc40SCorentin Chary 		goto error_debugfs;
10395b80fc40SCorentin Chary 
10405b80fc40SCorentin Chary 	dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
10415b80fc40SCorentin Chary 				 &samsung->debug.data.d3);
10425b80fc40SCorentin Chary 	if (!dent)
10435b80fc40SCorentin Chary 		goto error_debugfs;
10445b80fc40SCorentin Chary 
10455b80fc40SCorentin Chary 	dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
10465b80fc40SCorentin Chary 				   samsung->debug.root,
10475b80fc40SCorentin Chary 				   &samsung->debug.data_wrapper);
10485b80fc40SCorentin Chary 	if (!dent)
10495b80fc40SCorentin Chary 		goto error_debugfs;
10505b80fc40SCorentin Chary 
10515b80fc40SCorentin Chary 	dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
10525b80fc40SCorentin Chary 				   samsung->debug.root,
10535b80fc40SCorentin Chary 				   &samsung->debug.f0000_wrapper);
10545b80fc40SCorentin Chary 	if (!dent)
10555b80fc40SCorentin Chary 		goto error_debugfs;
10565b80fc40SCorentin Chary 
10575b80fc40SCorentin Chary 	dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
10585b80fc40SCorentin Chary 				   samsung->debug.root, samsung,
10595b80fc40SCorentin Chary 				   &samsung_laptop_call_io_ops);
10605b80fc40SCorentin Chary 	if (!dent)
10615b80fc40SCorentin Chary 		goto error_debugfs;
10625b80fc40SCorentin Chary 
10635b80fc40SCorentin Chary 	return 0;
10645b80fc40SCorentin Chary 
10655b80fc40SCorentin Chary error_debugfs:
10665b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
10675b80fc40SCorentin Chary 	return -ENOMEM;
10685b80fc40SCorentin Chary }
10695b80fc40SCorentin Chary 
10705dea7a20SCorentin Chary static void samsung_sabi_exit(struct samsung_laptop *samsung)
10715dea7a20SCorentin Chary {
10725dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
10735dea7a20SCorentin Chary 
10745dea7a20SCorentin Chary 	/* Turn off "Linux" mode in the BIOS */
10755dea7a20SCorentin Chary 	if (config && config->commands.set_linux != 0xff)
10767e960711SCorentin Chary 		sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
10775dea7a20SCorentin Chary 
10785dea7a20SCorentin Chary 	if (samsung->sabi_iface) {
10795dea7a20SCorentin Chary 		iounmap(samsung->sabi_iface);
10805dea7a20SCorentin Chary 		samsung->sabi_iface = NULL;
10815dea7a20SCorentin Chary 	}
10825dea7a20SCorentin Chary 	if (samsung->f0000_segment) {
10835dea7a20SCorentin Chary 		iounmap(samsung->f0000_segment);
10845dea7a20SCorentin Chary 		samsung->f0000_segment = NULL;
10855dea7a20SCorentin Chary 	}
10865dea7a20SCorentin Chary 
10875dea7a20SCorentin Chary 	samsung->config = NULL;
10885dea7a20SCorentin Chary }
10895dea7a20SCorentin Chary 
109049dd7730SCorentin Chary static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
109149dd7730SCorentin Chary 				      unsigned int ifaceP)
10925dea7a20SCorentin Chary {
10935dea7a20SCorentin Chary 	const struct sabi_config *config = samsung->config;
10945dea7a20SCorentin Chary 
10955dea7a20SCorentin Chary 	printk(KERN_DEBUG "This computer supports SABI==%x\n",
10965dea7a20SCorentin Chary 	       loca + 0xf0000 - 6);
109749dd7730SCorentin Chary 
10985dea7a20SCorentin Chary 	printk(KERN_DEBUG "SABI header:\n");
10995dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
11005dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.port));
11015dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
11025dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.iface_func));
11035dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
11045dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.en_mem));
11055dea7a20SCorentin Chary 	printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
11065dea7a20SCorentin Chary 	       readb(samsung->sabi + config->header_offsets.re_mem));
11075dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
11085dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_offset));
11095dea7a20SCorentin Chary 	printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
11105dea7a20SCorentin Chary 	       readw(samsung->sabi + config->header_offsets.data_segment));
11115dea7a20SCorentin Chary 
111249dd7730SCorentin Chary 	printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
11135dea7a20SCorentin Chary }
11145dea7a20SCorentin Chary 
11155dea7a20SCorentin Chary static int __init samsung_sabi_init(struct samsung_laptop *samsung)
11165dea7a20SCorentin Chary {
11175dea7a20SCorentin Chary 	const struct sabi_config *config = NULL;
11185dea7a20SCorentin Chary 	const struct sabi_commands *commands;
11195dea7a20SCorentin Chary 	unsigned int ifaceP;
11205dea7a20SCorentin Chary 	int ret = 0;
11215dea7a20SCorentin Chary 	int i;
11225dea7a20SCorentin Chary 	int loca;
11235dea7a20SCorentin Chary 
11245dea7a20SCorentin Chary 	samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
11255dea7a20SCorentin Chary 	if (!samsung->f0000_segment) {
11265dea7a20SCorentin Chary 		pr_err("Can't map the segment at 0xf0000\n");
11275dea7a20SCorentin Chary 		ret = -EINVAL;
11285dea7a20SCorentin Chary 		goto exit;
11295dea7a20SCorentin Chary 	}
11305dea7a20SCorentin Chary 
11315dea7a20SCorentin Chary 	/* Try to find one of the signatures in memory to find the header */
11325dea7a20SCorentin Chary 	for (i = 0; sabi_configs[i].test_string != 0; ++i) {
11335dea7a20SCorentin Chary 		samsung->config = &sabi_configs[i];
11345dea7a20SCorentin Chary 		loca = find_signature(samsung->f0000_segment,
11355dea7a20SCorentin Chary 				      samsung->config->test_string);
11365dea7a20SCorentin Chary 		if (loca != 0xffff)
11375dea7a20SCorentin Chary 			break;
11385dea7a20SCorentin Chary 	}
11395dea7a20SCorentin Chary 
11405dea7a20SCorentin Chary 	if (loca == 0xffff) {
11415dea7a20SCorentin Chary 		pr_err("This computer does not support SABI\n");
11425dea7a20SCorentin Chary 		ret = -ENODEV;
11435dea7a20SCorentin Chary 		goto exit;
11445dea7a20SCorentin Chary 	}
11455dea7a20SCorentin Chary 
11465dea7a20SCorentin Chary 	config = samsung->config;
11475dea7a20SCorentin Chary 	commands = &config->commands;
11485dea7a20SCorentin Chary 
11495dea7a20SCorentin Chary 	/* point to the SMI port Number */
11505dea7a20SCorentin Chary 	loca += 1;
11515dea7a20SCorentin Chary 	samsung->sabi = (samsung->f0000_segment + loca);
11525dea7a20SCorentin Chary 
11535dea7a20SCorentin Chary 	/* Get a pointer to the SABI Interface */
11545dea7a20SCorentin Chary 	ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
11555dea7a20SCorentin Chary 	ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
115649dd7730SCorentin Chary 
115749dd7730SCorentin Chary 	if (debug)
115849dd7730SCorentin Chary 		samsung_sabi_infos(samsung, loca, ifaceP);
115949dd7730SCorentin Chary 
11605dea7a20SCorentin Chary 	samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
11615dea7a20SCorentin Chary 	if (!samsung->sabi_iface) {
11625dea7a20SCorentin Chary 		pr_err("Can't remap %x\n", ifaceP);
11635dea7a20SCorentin Chary 		ret = -EINVAL;
11645dea7a20SCorentin Chary 		goto exit;
11655dea7a20SCorentin Chary 	}
11665dea7a20SCorentin Chary 
11675dea7a20SCorentin Chary 	/* Turn on "Linux" mode in the BIOS */
11685dea7a20SCorentin Chary 	if (commands->set_linux != 0xff) {
11697e960711SCorentin Chary 		int retval = sabi_set_commandb(samsung,
11705dea7a20SCorentin Chary 					       commands->set_linux, 0x81);
11715dea7a20SCorentin Chary 		if (retval) {
11725dea7a20SCorentin Chary 			pr_warn("Linux mode was not set!\n");
11735dea7a20SCorentin Chary 			ret = -ENODEV;
11745dea7a20SCorentin Chary 			goto exit;
11755dea7a20SCorentin Chary 		}
11765dea7a20SCorentin Chary 	}
11775dea7a20SCorentin Chary 
11785dea7a20SCorentin Chary 	/* Check for stepping quirk */
1179f34cd9caSCorentin Chary 	if (samsung->handle_backlight)
11805dea7a20SCorentin Chary 		check_for_stepping_quirk(samsung);
11815dea7a20SCorentin Chary 
11825dea7a20SCorentin Chary exit:
11835dea7a20SCorentin Chary 	if (ret)
11845dea7a20SCorentin Chary 		samsung_sabi_exit(samsung);
11855dea7a20SCorentin Chary 
11865dea7a20SCorentin Chary 	return ret;
11875dea7a20SCorentin Chary }
11885dea7a20SCorentin Chary 
11895dea7a20SCorentin Chary static void samsung_platform_exit(struct samsung_laptop *samsung)
11905dea7a20SCorentin Chary {
11915dea7a20SCorentin Chary 	if (samsung->platform_device) {
11925dea7a20SCorentin Chary 		platform_device_unregister(samsung->platform_device);
11935dea7a20SCorentin Chary 		samsung->platform_device = NULL;
11945dea7a20SCorentin Chary 	}
11955dea7a20SCorentin Chary }
11965dea7a20SCorentin Chary 
11975dea7a20SCorentin Chary static int __init samsung_platform_init(struct samsung_laptop *samsung)
11985dea7a20SCorentin Chary {
11995dea7a20SCorentin Chary 	struct platform_device *pdev;
12005dea7a20SCorentin Chary 
12015dea7a20SCorentin Chary 	pdev = platform_device_register_simple("samsung", -1, NULL, 0);
12025dea7a20SCorentin Chary 	if (IS_ERR(pdev))
12035dea7a20SCorentin Chary 		return PTR_ERR(pdev);
12045dea7a20SCorentin Chary 
12055dea7a20SCorentin Chary 	samsung->platform_device = pdev;
12065dea7a20SCorentin Chary 	platform_set_drvdata(samsung->platform_device, samsung);
12075dea7a20SCorentin Chary 	return 0;
12085dea7a20SCorentin Chary }
12095dea7a20SCorentin Chary 
12102d70b73aSGreg Kroah-Hartman static int __init dmi_check_cb(const struct dmi_system_id *id)
12112d70b73aSGreg Kroah-Hartman {
12125dea7a20SCorentin Chary 	pr_info("found laptop model '%s'\n", id->ident);
121327836584SAxel Lin 	return 1;
12142d70b73aSGreg Kroah-Hartman }
12152d70b73aSGreg Kroah-Hartman 
12162d70b73aSGreg Kroah-Hartman static struct dmi_system_id __initdata samsung_dmi_table[] = {
12172d70b73aSGreg Kroah-Hartman 	{
12182d70b73aSGreg Kroah-Hartman 		.ident = "N128",
12192d70b73aSGreg Kroah-Hartman 		.matches = {
12202d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12212d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12222d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
12232d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "N128"),
12242d70b73aSGreg Kroah-Hartman 		},
12252d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12262d70b73aSGreg Kroah-Hartman 	},
12272d70b73aSGreg Kroah-Hartman 	{
12282d70b73aSGreg Kroah-Hartman 		.ident = "N130",
12292d70b73aSGreg Kroah-Hartman 		.matches = {
12302d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12312d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12322d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
12332d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "N130"),
12342d70b73aSGreg Kroah-Hartman 		},
12352d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12362d70b73aSGreg Kroah-Hartman 	},
12372d70b73aSGreg Kroah-Hartman 	{
12384e2441c0SJ Witteveen 		.ident = "N510",
12394e2441c0SJ Witteveen 		.matches = {
12404e2441c0SJ Witteveen 			DMI_MATCH(DMI_SYS_VENDOR,
12414e2441c0SJ Witteveen 					"SAMSUNG ELECTRONICS CO., LTD."),
12424e2441c0SJ Witteveen 			DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
12434e2441c0SJ Witteveen 			DMI_MATCH(DMI_BOARD_NAME, "N510"),
12444e2441c0SJ Witteveen 		},
12454e2441c0SJ Witteveen 		.callback = dmi_check_cb,
12464e2441c0SJ Witteveen 	},
12474e2441c0SJ Witteveen 	{
12482d70b73aSGreg Kroah-Hartman 		.ident = "X125",
12492d70b73aSGreg Kroah-Hartman 		.matches = {
12502d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12512d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12522d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
12532d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "X125"),
12542d70b73aSGreg Kroah-Hartman 		},
12552d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12562d70b73aSGreg Kroah-Hartman 	},
12572d70b73aSGreg Kroah-Hartman 	{
12582d70b73aSGreg Kroah-Hartman 		.ident = "X120/X170",
12592d70b73aSGreg Kroah-Hartman 		.matches = {
12602d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12612d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12622d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
12632d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
12642d70b73aSGreg Kroah-Hartman 		},
12652d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12662d70b73aSGreg Kroah-Hartman 	},
12672d70b73aSGreg Kroah-Hartman 	{
12682d70b73aSGreg Kroah-Hartman 		.ident = "NC10",
12692d70b73aSGreg Kroah-Hartman 		.matches = {
12702d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12712d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12722d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
12732d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "NC10"),
12742d70b73aSGreg Kroah-Hartman 		},
12752d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12762d70b73aSGreg Kroah-Hartman 	},
12772d70b73aSGreg Kroah-Hartman 		{
12782d70b73aSGreg Kroah-Hartman 		.ident = "NP-Q45",
12792d70b73aSGreg Kroah-Hartman 		.matches = {
12802d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12812d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12822d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
12832d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
12842d70b73aSGreg Kroah-Hartman 		},
12852d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12862d70b73aSGreg Kroah-Hartman 		},
12872d70b73aSGreg Kroah-Hartman 	{
12882d70b73aSGreg Kroah-Hartman 		.ident = "X360",
12892d70b73aSGreg Kroah-Hartman 		.matches = {
12902d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
12912d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
12922d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
12932d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "X360"),
12942d70b73aSGreg Kroah-Hartman 		},
12952d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
12962d70b73aSGreg Kroah-Hartman 	},
12972d70b73aSGreg Kroah-Hartman 	{
12983d536ed4SAlberto Mardegan 		.ident = "R410 Plus",
12993d536ed4SAlberto Mardegan 		.matches = {
13003d536ed4SAlberto Mardegan 			DMI_MATCH(DMI_SYS_VENDOR,
13013d536ed4SAlberto Mardegan 					"SAMSUNG ELECTRONICS CO., LTD."),
13023d536ed4SAlberto Mardegan 			DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
13033d536ed4SAlberto Mardegan 			DMI_MATCH(DMI_BOARD_NAME, "R460"),
13043d536ed4SAlberto Mardegan 		},
13053d536ed4SAlberto Mardegan 		.callback = dmi_check_cb,
13063d536ed4SAlberto Mardegan 	},
13073d536ed4SAlberto Mardegan 	{
13082d70b73aSGreg Kroah-Hartman 		.ident = "R518",
13092d70b73aSGreg Kroah-Hartman 		.matches = {
13102d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
13112d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
13122d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
13132d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "R518"),
13142d70b73aSGreg Kroah-Hartman 		},
13152d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13162d70b73aSGreg Kroah-Hartman 	},
13172d70b73aSGreg Kroah-Hartman 	{
13182d70b73aSGreg Kroah-Hartman 		.ident = "R519/R719",
13192d70b73aSGreg Kroah-Hartman 		.matches = {
13202d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
13212d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
13222d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
13232d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
13242d70b73aSGreg Kroah-Hartman 		},
13252d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13262d70b73aSGreg Kroah-Hartman 	},
13272d70b73aSGreg Kroah-Hartman 	{
132878a7539bSThomas Courbon 		.ident = "N150/N210/N220",
132978a7539bSThomas Courbon 		.matches = {
133078a7539bSThomas Courbon 			DMI_MATCH(DMI_SYS_VENDOR,
133178a7539bSThomas Courbon 					"SAMSUNG ELECTRONICS CO., LTD."),
133278a7539bSThomas Courbon 			DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
133378a7539bSThomas Courbon 			DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
133478a7539bSThomas Courbon 		},
133578a7539bSThomas Courbon 		.callback = dmi_check_cb,
133678a7539bSThomas Courbon 	},
133778a7539bSThomas Courbon 	{
1338f689c875SRaul Gutierrez Segales 		.ident = "N220",
1339f689c875SRaul Gutierrez Segales 		.matches = {
1340f689c875SRaul Gutierrez Segales 			DMI_MATCH(DMI_SYS_VENDOR,
1341f689c875SRaul Gutierrez Segales 					"SAMSUNG ELECTRONICS CO., LTD."),
1342f689c875SRaul Gutierrez Segales 			DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
1343f689c875SRaul Gutierrez Segales 			DMI_MATCH(DMI_BOARD_NAME, "N220"),
1344f689c875SRaul Gutierrez Segales 		},
1345f689c875SRaul Gutierrez Segales 		.callback = dmi_check_cb,
1346f689c875SRaul Gutierrez Segales 	},
1347f689c875SRaul Gutierrez Segales 	{
134810165072SGreg Kroah-Hartman 		.ident = "N150/N210/N220/N230",
13492d70b73aSGreg Kroah-Hartman 		.matches = {
13502d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
13512d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
135210165072SGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
135310165072SGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
13542d70b73aSGreg Kroah-Hartman 		},
13552d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13562d70b73aSGreg Kroah-Hartman 	},
13572d70b73aSGreg Kroah-Hartman 	{
13582d70b73aSGreg Kroah-Hartman 		.ident = "N150P/N210P/N220P",
13592d70b73aSGreg Kroah-Hartman 		.matches = {
13602d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
13612d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
13622d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
13632d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
13642d70b73aSGreg Kroah-Hartman 		},
13652d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13662d70b73aSGreg Kroah-Hartman 	},
13672d70b73aSGreg Kroah-Hartman 	{
1368f87d0299SStefan Beller 		.ident = "R700",
1369f87d0299SStefan Beller 		.matches = {
1370f87d0299SStefan Beller 		      DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1371f87d0299SStefan Beller 		      DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
1372f87d0299SStefan Beller 		      DMI_MATCH(DMI_BOARD_NAME, "SR700"),
1373f87d0299SStefan Beller 		},
1374f87d0299SStefan Beller 		.callback = dmi_check_cb,
1375f87d0299SStefan Beller 	},
1376f87d0299SStefan Beller 	{
13772d70b73aSGreg Kroah-Hartman 		.ident = "R530/R730",
13782d70b73aSGreg Kroah-Hartman 		.matches = {
13792d70b73aSGreg Kroah-Hartman 		      DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
13802d70b73aSGreg Kroah-Hartman 		      DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
13812d70b73aSGreg Kroah-Hartman 		      DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
13822d70b73aSGreg Kroah-Hartman 		},
13832d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13842d70b73aSGreg Kroah-Hartman 	},
13852d70b73aSGreg Kroah-Hartman 	{
13862d70b73aSGreg Kroah-Hartman 		.ident = "NF110/NF210/NF310",
13872d70b73aSGreg Kroah-Hartman 		.matches = {
13882d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
13892d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
13902d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
13912d70b73aSGreg Kroah-Hartman 		},
13922d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
13932d70b73aSGreg Kroah-Hartman 	},
13942d70b73aSGreg Kroah-Hartman 	{
13952d70b73aSGreg Kroah-Hartman 		.ident = "N145P/N250P/N260P",
13962d70b73aSGreg Kroah-Hartman 		.matches = {
13972d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
13982d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
13992d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
14002d70b73aSGreg Kroah-Hartman 		},
14012d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
14022d70b73aSGreg Kroah-Hartman 	},
14032d70b73aSGreg Kroah-Hartman 	{
14042d70b73aSGreg Kroah-Hartman 		.ident = "R70/R71",
14052d70b73aSGreg Kroah-Hartman 		.matches = {
14062d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR,
14072d70b73aSGreg Kroah-Hartman 					"SAMSUNG ELECTRONICS CO., LTD."),
14082d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
14092d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
14102d70b73aSGreg Kroah-Hartman 		},
14112d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
14122d70b73aSGreg Kroah-Hartman 	},
14132d70b73aSGreg Kroah-Hartman 	{
14142d70b73aSGreg Kroah-Hartman 		.ident = "P460",
14152d70b73aSGreg Kroah-Hartman 		.matches = {
14162d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
14172d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
14182d70b73aSGreg Kroah-Hartman 			DMI_MATCH(DMI_BOARD_NAME, "P460"),
14192d70b73aSGreg Kroah-Hartman 		},
14202d70b73aSGreg Kroah-Hartman 		.callback = dmi_check_cb,
14212d70b73aSGreg Kroah-Hartman 	},
1422093ed561SSmelov Andrey 	{
1423093ed561SSmelov Andrey 		.ident = "R528/R728",
1424093ed561SSmelov Andrey 		.matches = {
1425093ed561SSmelov Andrey 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1426093ed561SSmelov Andrey 			DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
1427093ed561SSmelov Andrey 			DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
1428093ed561SSmelov Andrey 		},
1429093ed561SSmelov Andrey 		.callback = dmi_check_cb,
1430093ed561SSmelov Andrey 	},
14317b3c257cSJason Stubbs 	{
14327b3c257cSJason Stubbs 		.ident = "NC210/NC110",
14337b3c257cSJason Stubbs 		.matches = {
14347b3c257cSJason Stubbs 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
14357b3c257cSJason Stubbs 			DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
14367b3c257cSJason Stubbs 			DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
14377b3c257cSJason Stubbs 		},
14387b3c257cSJason Stubbs 		.callback = dmi_check_cb,
14397b3c257cSJason Stubbs 	},
14407500eeb0STommaso Massimi 		{
14417500eeb0STommaso Massimi 		.ident = "X520",
14427500eeb0STommaso Massimi 		.matches = {
14437500eeb0STommaso Massimi 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
14447500eeb0STommaso Massimi 			DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
14457500eeb0STommaso Massimi 			DMI_MATCH(DMI_BOARD_NAME, "X520"),
14467500eeb0STommaso Massimi 		},
14477500eeb0STommaso Massimi 		.callback = dmi_check_cb,
14487500eeb0STommaso Massimi 	},
14492d70b73aSGreg Kroah-Hartman 	{ },
14502d70b73aSGreg Kroah-Hartman };
14512d70b73aSGreg Kroah-Hartman MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
14522d70b73aSGreg Kroah-Hartman 
14535dea7a20SCorentin Chary static struct platform_device *samsung_platform_device;
14542d70b73aSGreg Kroah-Hartman 
14552d70b73aSGreg Kroah-Hartman static int __init samsung_init(void)
14562d70b73aSGreg Kroah-Hartman {
14575dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
14585dea7a20SCorentin Chary 	int ret;
14592d70b73aSGreg Kroah-Hartman 
14602d70b73aSGreg Kroah-Hartman 	if (!force && !dmi_check_system(samsung_dmi_table))
14612d70b73aSGreg Kroah-Hartman 		return -ENODEV;
14622d70b73aSGreg Kroah-Hartman 
1463a6df4894SCorentin Chary 	samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1464a6df4894SCorentin Chary 	if (!samsung)
1465a6df4894SCorentin Chary 		return -ENOMEM;
1466a6df4894SCorentin Chary 
1467a6df4894SCorentin Chary 	mutex_init(&samsung->sabi_mutex);
1468f34cd9caSCorentin Chary 	samsung->handle_backlight = true;
1469f34cd9caSCorentin Chary 
1470f34cd9caSCorentin Chary #ifdef CONFIG_ACPI
1471f34cd9caSCorentin Chary 	/* Don't handle backlight here if the acpi video already handle it */
1472f34cd9caSCorentin Chary 	if (acpi_video_backlight_support()) {
1473f34cd9caSCorentin Chary 		pr_info("Backlight controlled by ACPI video driver\n");
1474f34cd9caSCorentin Chary 		samsung->handle_backlight = false;
1475f34cd9caSCorentin Chary 	}
1476f34cd9caSCorentin Chary #endif
1477a6df4894SCorentin Chary 
14785dea7a20SCorentin Chary 	ret = samsung_platform_init(samsung);
14795dea7a20SCorentin Chary 	if (ret)
14805dea7a20SCorentin Chary 		goto error_platform;
14812d70b73aSGreg Kroah-Hartman 
14825dea7a20SCorentin Chary 	ret = samsung_sabi_init(samsung);
14835dea7a20SCorentin Chary 	if (ret)
14845dea7a20SCorentin Chary 		goto error_sabi;
14852d70b73aSGreg Kroah-Hartman 
14865dea7a20SCorentin Chary 	ret = samsung_sysfs_init(samsung);
14875dea7a20SCorentin Chary 	if (ret)
14885dea7a20SCorentin Chary 		goto error_sysfs;
14892d70b73aSGreg Kroah-Hartman 
14905dea7a20SCorentin Chary 	ret = samsung_backlight_init(samsung);
14915dea7a20SCorentin Chary 	if (ret)
14925dea7a20SCorentin Chary 		goto error_backlight;
1493a6df4894SCorentin Chary 
14945dea7a20SCorentin Chary 	ret = samsung_rfkill_init(samsung);
14955dea7a20SCorentin Chary 	if (ret)
14965dea7a20SCorentin Chary 		goto error_rfkill;
14972d70b73aSGreg Kroah-Hartman 
1498f674ebf1SCorentin Chary 	ret = samsung_leds_init(samsung);
1499f674ebf1SCorentin Chary 	if (ret)
1500f674ebf1SCorentin Chary 		goto error_leds;
1501f674ebf1SCorentin Chary 
15025b80fc40SCorentin Chary 	ret = samsung_debugfs_init(samsung);
15035b80fc40SCorentin Chary 	if (ret)
15045b80fc40SCorentin Chary 		goto error_debugfs;
15055b80fc40SCorentin Chary 
15065dea7a20SCorentin Chary 	samsung_platform_device = samsung->platform_device;
15075dea7a20SCorentin Chary 	return ret;
15082d70b73aSGreg Kroah-Hartman 
15095b80fc40SCorentin Chary error_debugfs:
1510f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
1511f674ebf1SCorentin Chary error_leds:
15125b80fc40SCorentin Chary 	samsung_rfkill_exit(samsung);
15135dea7a20SCorentin Chary error_rfkill:
15145dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
15155dea7a20SCorentin Chary error_backlight:
15165dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
15175dea7a20SCorentin Chary error_sysfs:
15185dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
15195dea7a20SCorentin Chary error_sabi:
15205dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
15215dea7a20SCorentin Chary error_platform:
1522a6df4894SCorentin Chary 	kfree(samsung);
15235dea7a20SCorentin Chary 	return ret;
15242d70b73aSGreg Kroah-Hartman }
15252d70b73aSGreg Kroah-Hartman 
15262d70b73aSGreg Kroah-Hartman static void __exit samsung_exit(void)
15272d70b73aSGreg Kroah-Hartman {
15285dea7a20SCorentin Chary 	struct samsung_laptop *samsung;
15292d70b73aSGreg Kroah-Hartman 
15305dea7a20SCorentin Chary 	samsung = platform_get_drvdata(samsung_platform_device);
1531a6df4894SCorentin Chary 
15325b80fc40SCorentin Chary 	samsung_debugfs_exit(samsung);
1533f674ebf1SCorentin Chary 	samsung_leds_exit(samsung);
15345dea7a20SCorentin Chary 	samsung_rfkill_exit(samsung);
15355dea7a20SCorentin Chary 	samsung_backlight_exit(samsung);
15365dea7a20SCorentin Chary 	samsung_sysfs_exit(samsung);
15375dea7a20SCorentin Chary 	samsung_sabi_exit(samsung);
15385dea7a20SCorentin Chary 	samsung_platform_exit(samsung);
15395dea7a20SCorentin Chary 
1540a6df4894SCorentin Chary 	kfree(samsung);
15415dea7a20SCorentin Chary 	samsung_platform_device = NULL;
15422d70b73aSGreg Kroah-Hartman }
15432d70b73aSGreg Kroah-Hartman 
15442d70b73aSGreg Kroah-Hartman module_init(samsung_init);
15452d70b73aSGreg Kroah-Hartman module_exit(samsung_exit);
15462d70b73aSGreg Kroah-Hartman 
15472d70b73aSGreg Kroah-Hartman MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
15482d70b73aSGreg Kroah-Hartman MODULE_DESCRIPTION("Samsung Backlight driver");
15492d70b73aSGreg Kroah-Hartman MODULE_LICENSE("GPL");
1550