1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Samsung Laptop driver
4 *
5 * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
6 * Copyright (C) 2009,2011 Novell Inc.
7 */
8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
10#include <linux/kernel.h>
11#include <linux/init.h>
12#include <linux/module.h>
13#include <linux/delay.h>
14#include <linux/pci.h>
15#include <linux/backlight.h>
16#include <linux/leds.h>
17#include <linux/fb.h>
18#include <linux/dmi.h>
19#include <linux/platform_device.h>
20#include <linux/rfkill.h>
21#include <linux/acpi.h>
22#include <linux/seq_file.h>
23#include <linux/debugfs.h>
24#include <linux/ctype.h>
25#include <linux/efi.h>
26#include <linux/suspend.h>
27#include <acpi/video.h>
28
29/*
30 * This driver is needed because a number of Samsung laptops do not hook
31 * their control settings through ACPI.  So we have to poke around in the
32 * BIOS to do things like brightness values, and "special" key controls.
33 */
34
35/*
36 * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
37 * be reserved by the BIOS (which really doesn't make much sense), we tell
38 * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
39 */
40#define MAX_BRIGHT	0x07
41
42
43#define SABI_IFACE_MAIN			0x00
44#define SABI_IFACE_SUB			0x02
45#define SABI_IFACE_COMPLETE		0x04
46#define SABI_IFACE_DATA			0x05
47
48#define WL_STATUS_WLAN			0x0
49#define WL_STATUS_BT			0x2
50
51/* Structure get/set data using sabi */
52struct sabi_data {
53	union {
54		struct {
55			u32 d0;
56			u32 d1;
57			u16 d2;
58			u8  d3;
59		};
60		u8 data[11];
61	};
62};
63
64struct sabi_header_offsets {
65	u8 port;
66	u8 re_mem;
67	u8 iface_func;
68	u8 en_mem;
69	u8 data_offset;
70	u8 data_segment;
71};
72
73struct sabi_commands {
74	/*
75	 * Brightness is 0 - 8, as described above.
76	 * Value 0 is for the BIOS to use
77	 */
78	u16 get_brightness;
79	u16 set_brightness;
80
81	/*
82	 * first byte:
83	 * 0x00 - wireless is off
84	 * 0x01 - wireless is on
85	 * second byte:
86	 * 0x02 - 3G is off
87	 * 0x03 - 3G is on
88	 * TODO, verify 3G is correct, that doesn't seem right...
89	 */
90	u16 get_wireless_button;
91	u16 set_wireless_button;
92
93	/* 0 is off, 1 is on */
94	u16 get_backlight;
95	u16 set_backlight;
96
97	/*
98	 * 0x80 or 0x00 - no action
99	 * 0x81 - recovery key pressed
100	 */
101	u16 get_recovery_mode;
102	u16 set_recovery_mode;
103
104	/*
105	 * on seclinux: 0 is low, 1 is high,
106	 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
107	 */
108	u16 get_performance_level;
109	u16 set_performance_level;
110
111	/* 0x80 is off, 0x81 is on */
112	u16 get_battery_life_extender;
113	u16 set_battery_life_extender;
114
115	/* 0x80 is off, 0x81 is on */
116	u16 get_usb_charge;
117	u16 set_usb_charge;
118
119	/* the first byte is for bluetooth and the third one is for wlan */
120	u16 get_wireless_status;
121	u16 set_wireless_status;
122
123	/* 0x80 is off, 0x81 is on */
124	u16 get_lid_handling;
125	u16 set_lid_handling;
126
127	/* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
128	u16 kbd_backlight;
129
130	/*
131	 * Tell the BIOS that Linux is running on this machine.
132	 * 81 is on, 80 is off
133	 */
134	u16 set_linux;
135};
136
137struct sabi_performance_level {
138	const char *name;
139	u16 value;
140};
141
142struct sabi_config {
143	int sabi_version;
144	const char *test_string;
145	u16 main_function;
146	const struct sabi_header_offsets header_offsets;
147	const struct sabi_commands commands;
148	const struct sabi_performance_level performance_levels[4];
149	u8 min_brightness;
150	u8 max_brightness;
151};
152
153static const struct sabi_config sabi_configs[] = {
154	{
155		/* I don't know if it is really 2, but it it is
156		 * less than 3 anyway */
157		.sabi_version = 2,
158
159		.test_string = "SECLINUX",
160
161		.main_function = 0x4c49,
162
163		.header_offsets = {
164			.port = 0x00,
165			.re_mem = 0x02,
166			.iface_func = 0x03,
167			.en_mem = 0x04,
168			.data_offset = 0x05,
169			.data_segment = 0x07,
170		},
171
172		.commands = {
173			.get_brightness = 0x00,
174			.set_brightness = 0x01,
175
176			.get_wireless_button = 0x02,
177			.set_wireless_button = 0x03,
178
179			.get_backlight = 0x04,
180			.set_backlight = 0x05,
181
182			.get_recovery_mode = 0x06,
183			.set_recovery_mode = 0x07,
184
185			.get_performance_level = 0x08,
186			.set_performance_level = 0x09,
187
188			.get_battery_life_extender = 0xFFFF,
189			.set_battery_life_extender = 0xFFFF,
190
191			.get_usb_charge = 0xFFFF,
192			.set_usb_charge = 0xFFFF,
193
194			.get_wireless_status = 0xFFFF,
195			.set_wireless_status = 0xFFFF,
196
197			.get_lid_handling = 0xFFFF,
198			.set_lid_handling = 0xFFFF,
199
200			.kbd_backlight = 0xFFFF,
201
202			.set_linux = 0x0a,
203		},
204
205		.performance_levels = {
206			{
207				.name = "silent",
208				.value = 0,
209			},
210			{
211				.name = "normal",
212				.value = 1,
213			},
214			{ },
215		},
216		.min_brightness = 1,
217		.max_brightness = 8,
218	},
219	{
220		.sabi_version = 3,
221
222		.test_string = "SwSmi@",
223
224		.main_function = 0x5843,
225
226		.header_offsets = {
227			.port = 0x00,
228			.re_mem = 0x04,
229			.iface_func = 0x02,
230			.en_mem = 0x03,
231			.data_offset = 0x05,
232			.data_segment = 0x07,
233		},
234
235		.commands = {
236			.get_brightness = 0x10,
237			.set_brightness = 0x11,
238
239			.get_wireless_button = 0x12,
240			.set_wireless_button = 0x13,
241
242			.get_backlight = 0x2d,
243			.set_backlight = 0x2e,
244
245			.get_recovery_mode = 0xff,
246			.set_recovery_mode = 0xff,
247
248			.get_performance_level = 0x31,
249			.set_performance_level = 0x32,
250
251			.get_battery_life_extender = 0x65,
252			.set_battery_life_extender = 0x66,
253
254			.get_usb_charge = 0x67,
255			.set_usb_charge = 0x68,
256
257			.get_wireless_status = 0x69,
258			.set_wireless_status = 0x6a,
259
260			.get_lid_handling = 0x6d,
261			.set_lid_handling = 0x6e,
262
263			.kbd_backlight = 0x78,
264
265			.set_linux = 0xff,
266		},
267
268		.performance_levels = {
269			{
270				.name = "normal",
271				.value = 0,
272			},
273			{
274				.name = "silent",
275				.value = 1,
276			},
277			{
278				.name = "overclock",
279				.value = 2,
280			},
281			{ },
282		},
283		.min_brightness = 0,
284		.max_brightness = 8,
285	},
286	{ },
287};
288
289/*
290 * samsung-laptop/    - debugfs root directory
291 *   f0000_segment    - dump f0000 segment
292 *   command          - current command
293 *   data             - current data
294 *   d0, d1, d2, d3   - data fields
295 *   call             - call SABI using command and data
296 *
297 * This allow to call arbitrary sabi commands wihout
298 * modifying the driver at all.
299 * For example, setting the keyboard backlight brightness to 5
300 *
301 *  echo 0x78 > command
302 *  echo 0x0582 > d0
303 *  echo 0 > d1
304 *  echo 0 > d2
305 *  echo 0 > d3
306 *  cat call
307 */
308
309struct samsung_laptop_debug {
310	struct dentry *root;
311	struct sabi_data data;
312	u16 command;
313
314	struct debugfs_blob_wrapper f0000_wrapper;
315	struct debugfs_blob_wrapper data_wrapper;
316	struct debugfs_blob_wrapper sdiag_wrapper;
317};
318
319struct samsung_laptop;
320
321struct samsung_rfkill {
322	struct samsung_laptop *samsung;
323	struct rfkill *rfkill;
324	enum rfkill_type type;
325};
326
327struct samsung_laptop {
328	const struct sabi_config *config;
329
330	void __iomem *sabi;
331	void __iomem *sabi_iface;
332	void __iomem *f0000_segment;
333
334	struct mutex sabi_mutex;
335
336	struct platform_device *platform_device;
337	struct backlight_device *backlight_device;
338
339	struct samsung_rfkill wlan;
340	struct samsung_rfkill bluetooth;
341
342	struct led_classdev kbd_led;
343	int kbd_led_wk;
344	struct workqueue_struct *led_workqueue;
345	struct work_struct kbd_led_work;
346
347	struct samsung_laptop_debug debug;
348	struct samsung_quirks *quirks;
349
350	struct notifier_block pm_nb;
351
352	bool handle_backlight;
353	bool has_stepping_quirk;
354
355	char sdiag[64];
356};
357
358struct samsung_quirks {
359	bool broken_acpi_video;
360	bool four_kbd_backlight_levels;
361	bool enable_kbd_backlight;
362	bool use_native_backlight;
363	bool lid_handling;
364};
365
366static struct samsung_quirks samsung_unknown = {};
367
368static struct samsung_quirks samsung_broken_acpi_video = {
369	.broken_acpi_video = true,
370};
371
372static struct samsung_quirks samsung_use_native_backlight = {
373	.use_native_backlight = true,
374};
375
376static struct samsung_quirks samsung_np740u3e = {
377	.four_kbd_backlight_levels = true,
378	.enable_kbd_backlight = true,
379};
380
381static struct samsung_quirks samsung_lid_handling = {
382	.lid_handling = true,
383};
384
385static bool force;
386module_param(force, bool, 0);
387MODULE_PARM_DESC(force,
388		"Disable the DMI check and forces the driver to be loaded");
389
390static bool debug;
391module_param(debug, bool, S_IRUGO | S_IWUSR);
392MODULE_PARM_DESC(debug, "Debug enabled or not");
393
394static int sabi_command(struct samsung_laptop *samsung, u16 command,
395			struct sabi_data *in,
396			struct sabi_data *out)
397{
398	const struct sabi_config *config = samsung->config;
399	int ret = 0;
400	u16 port = readw(samsung->sabi + config->header_offsets.port);
401	u8 complete, iface_data;
402
403	mutex_lock(&samsung->sabi_mutex);
404
405	if (debug) {
406		if (in)
407			pr_info("SABI command:0x%04x "
408				"data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
409				command, in->d0, in->d1, in->d2, in->d3);
410		else
411			pr_info("SABI command:0x%04x", command);
412	}
413
414	/* enable memory to be able to write to it */
415	outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
416
417	/* write out the command */
418	writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
419	writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
420	writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
421	if (in) {
422		writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
423		writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
424		writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
425		writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
426	}
427	outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
428
429	/* write protect memory to make it safe */
430	outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
431
432	/* see if the command actually succeeded */
433	complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
434	iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
435
436	/* iface_data = 0xFF happens when a command is not known
437	 * so we only add a warning in debug mode since we will
438	 * probably issue some unknown command at startup to find
439	 * out which features are supported */
440	if (complete != 0xaa || (iface_data == 0xff && debug))
441		pr_warn("SABI command 0x%04x failed with"
442			" completion flag 0x%02x and interface data 0x%02x",
443			command, complete, iface_data);
444
445	if (complete != 0xaa || iface_data == 0xff) {
446		ret = -EINVAL;
447		goto exit;
448	}
449
450	if (out) {
451		out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
452		out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
453		out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
454		out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
455	}
456
457	if (debug && out) {
458		pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
459			out->d0, out->d1, out->d2, out->d3);
460	}
461
462exit:
463	mutex_unlock(&samsung->sabi_mutex);
464	return ret;
465}
466
467/* simple wrappers usable with most commands */
468static int sabi_set_commandb(struct samsung_laptop *samsung,
469			     u16 command, u8 data)
470{
471	struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
472
473	in.data[0] = data;
474	return sabi_command(samsung, command, &in, NULL);
475}
476
477static int read_brightness(struct samsung_laptop *samsung)
478{
479	const struct sabi_config *config = samsung->config;
480	const struct sabi_commands *commands = &samsung->config->commands;
481	struct sabi_data sretval;
482	int user_brightness = 0;
483	int retval;
484
485	retval = sabi_command(samsung, commands->get_brightness,
486			      NULL, &sretval);
487	if (retval)
488		return retval;
489
490	user_brightness = sretval.data[0];
491	if (user_brightness > config->min_brightness)
492		user_brightness -= config->min_brightness;
493	else
494		user_brightness = 0;
495
496	return user_brightness;
497}
498
499static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
500{
501	const struct sabi_config *config = samsung->config;
502	const struct sabi_commands *commands = &samsung->config->commands;
503	u8 user_level = user_brightness + config->min_brightness;
504
505	if (samsung->has_stepping_quirk && user_level != 0) {
506		/*
507		 * short circuit if the specified level is what's already set
508		 * to prevent the screen from flickering needlessly
509		 */
510		if (user_brightness == read_brightness(samsung))
511			return;
512
513		sabi_set_commandb(samsung, commands->set_brightness, 0);
514	}
515
516	sabi_set_commandb(samsung, commands->set_brightness, user_level);
517}
518
519static int get_brightness(struct backlight_device *bd)
520{
521	struct samsung_laptop *samsung = bl_get_data(bd);
522
523	return read_brightness(samsung);
524}
525
526static void check_for_stepping_quirk(struct samsung_laptop *samsung)
527{
528	int initial_level;
529	int check_level;
530	int orig_level = read_brightness(samsung);
531
532	/*
533	 * Some laptops exhibit the strange behaviour of stepping toward
534	 * (rather than setting) the brightness except when changing to/from
535	 * brightness level 0. This behaviour is checked for here and worked
536	 * around in set_brightness.
537	 */
538
539	if (orig_level == 0)
540		set_brightness(samsung, 1);
541
542	initial_level = read_brightness(samsung);
543
544	if (initial_level <= 2)
545		check_level = initial_level + 2;
546	else
547		check_level = initial_level - 2;
548
549	samsung->has_stepping_quirk = false;
550	set_brightness(samsung, check_level);
551
552	if (read_brightness(samsung) != check_level) {
553		samsung->has_stepping_quirk = true;
554		pr_info("enabled workaround for brightness stepping quirk\n");
555	}
556
557	set_brightness(samsung, orig_level);
558}
559
560static int update_status(struct backlight_device *bd)
561{
562	struct samsung_laptop *samsung = bl_get_data(bd);
563	const struct sabi_commands *commands = &samsung->config->commands;
564
565	set_brightness(samsung, bd->props.brightness);
566
567	if (bd->props.power == FB_BLANK_UNBLANK)
568		sabi_set_commandb(samsung, commands->set_backlight, 1);
569	else
570		sabi_set_commandb(samsung, commands->set_backlight, 0);
571
572	return 0;
573}
574
575static const struct backlight_ops backlight_ops = {
576	.get_brightness	= get_brightness,
577	.update_status	= update_status,
578};
579
580static int seclinux_rfkill_set(void *data, bool blocked)
581{
582	struct samsung_rfkill *srfkill = data;
583	struct samsung_laptop *samsung = srfkill->samsung;
584	const struct sabi_commands *commands = &samsung->config->commands;
585
586	return sabi_set_commandb(samsung, commands->set_wireless_button,
587				 !blocked);
588}
589
590static const struct rfkill_ops seclinux_rfkill_ops = {
591	.set_block = seclinux_rfkill_set,
592};
593
594static int swsmi_wireless_status(struct samsung_laptop *samsung,
595				 struct sabi_data *data)
596{
597	const struct sabi_commands *commands = &samsung->config->commands;
598
599	return sabi_command(samsung, commands->get_wireless_status,
600			    NULL, data);
601}
602
603static int swsmi_rfkill_set(void *priv, bool blocked)
604{
605	struct samsung_rfkill *srfkill = priv;
606	struct samsung_laptop *samsung = srfkill->samsung;
607	const struct sabi_commands *commands = &samsung->config->commands;
608	struct sabi_data data;
609	int ret, i;
610
611	ret = swsmi_wireless_status(samsung, &data);
612	if (ret)
613		return ret;
614
615	/* Don't set the state for non-present devices */
616	for (i = 0; i < 4; i++)
617		if (data.data[i] == 0x02)
618			data.data[1] = 0;
619
620	if (srfkill->type == RFKILL_TYPE_WLAN)
621		data.data[WL_STATUS_WLAN] = !blocked;
622	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
623		data.data[WL_STATUS_BT] = !blocked;
624
625	return sabi_command(samsung, commands->set_wireless_status,
626			    &data, &data);
627}
628
629static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
630{
631	struct samsung_rfkill *srfkill = priv;
632	struct samsung_laptop *samsung = srfkill->samsung;
633	struct sabi_data data;
634	int ret;
635
636	ret = swsmi_wireless_status(samsung, &data);
637	if (ret)
638		return ;
639
640	if (srfkill->type == RFKILL_TYPE_WLAN)
641		ret = data.data[WL_STATUS_WLAN];
642	else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
643		ret = data.data[WL_STATUS_BT];
644	else
645		return ;
646
647	rfkill_set_sw_state(rfkill, !ret);
648}
649
650static const struct rfkill_ops swsmi_rfkill_ops = {
651	.set_block = swsmi_rfkill_set,
652	.query = swsmi_rfkill_query,
653};
654
655static ssize_t get_performance_level(struct device *dev,
656				     struct device_attribute *attr, char *buf)
657{
658	struct samsung_laptop *samsung = dev_get_drvdata(dev);
659	const struct sabi_config *config = samsung->config;
660	const struct sabi_commands *commands = &config->commands;
661	struct sabi_data sretval;
662	int retval;
663	int i;
664
665	/* Read the state */
666	retval = sabi_command(samsung, commands->get_performance_level,
667			      NULL, &sretval);
668	if (retval)
669		return retval;
670
671	/* The logic is backwards, yeah, lots of fun... */
672	for (i = 0; config->performance_levels[i].name; ++i) {
673		if (sretval.data[0] == config->performance_levels[i].value)
674			return sprintf(buf, "%s\n", config->performance_levels[i].name);
675	}
676	return sprintf(buf, "%s\n", "unknown");
677}
678
679static ssize_t set_performance_level(struct device *dev,
680				struct device_attribute *attr, const char *buf,
681				size_t count)
682{
683	struct samsung_laptop *samsung = dev_get_drvdata(dev);
684	const struct sabi_config *config = samsung->config;
685	const struct sabi_commands *commands = &config->commands;
686	int i;
687
688	if (count < 1)
689		return count;
690
691	for (i = 0; config->performance_levels[i].name; ++i) {
692		const struct sabi_performance_level *level =
693			&config->performance_levels[i];
694		if (!strncasecmp(level->name, buf, strlen(level->name))) {
695			sabi_set_commandb(samsung,
696					  commands->set_performance_level,
697					  level->value);
698			break;
699		}
700	}
701
702	if (!config->performance_levels[i].name)
703		return -EINVAL;
704
705	return count;
706}
707
708static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
709		   get_performance_level, set_performance_level);
710
711static int read_battery_life_extender(struct samsung_laptop *samsung)
712{
713	const struct sabi_commands *commands = &samsung->config->commands;
714	struct sabi_data data;
715	int retval;
716
717	if (commands->get_battery_life_extender == 0xFFFF)
718		return -ENODEV;
719
720	memset(&data, 0, sizeof(data));
721	data.data[0] = 0x80;
722	retval = sabi_command(samsung, commands->get_battery_life_extender,
723			      &data, &data);
724
725	if (retval)
726		return retval;
727
728	if (data.data[0] != 0 && data.data[0] != 1)
729		return -ENODEV;
730
731	return data.data[0];
732}
733
734static int write_battery_life_extender(struct samsung_laptop *samsung,
735				       int enabled)
736{
737	const struct sabi_commands *commands = &samsung->config->commands;
738	struct sabi_data data;
739
740	memset(&data, 0, sizeof(data));
741	data.data[0] = 0x80 | enabled;
742	return sabi_command(samsung, commands->set_battery_life_extender,
743			    &data, NULL);
744}
745
746static ssize_t get_battery_life_extender(struct device *dev,
747					 struct device_attribute *attr,
748					 char *buf)
749{
750	struct samsung_laptop *samsung = dev_get_drvdata(dev);
751	int ret;
752
753	ret = read_battery_life_extender(samsung);
754	if (ret < 0)
755		return ret;
756
757	return sprintf(buf, "%d\n", ret);
758}
759
760static ssize_t set_battery_life_extender(struct device *dev,
761					struct device_attribute *attr,
762					const char *buf, size_t count)
763{
764	struct samsung_laptop *samsung = dev_get_drvdata(dev);
765	int ret, value;
766
767	if (!count || kstrtoint(buf, 0, &value) != 0)
768		return -EINVAL;
769
770	ret = write_battery_life_extender(samsung, !!value);
771	if (ret < 0)
772		return ret;
773
774	return count;
775}
776
777static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
778		   get_battery_life_extender, set_battery_life_extender);
779
780static int read_usb_charge(struct samsung_laptop *samsung)
781{
782	const struct sabi_commands *commands = &samsung->config->commands;
783	struct sabi_data data;
784	int retval;
785
786	if (commands->get_usb_charge == 0xFFFF)
787		return -ENODEV;
788
789	memset(&data, 0, sizeof(data));
790	data.data[0] = 0x80;
791	retval = sabi_command(samsung, commands->get_usb_charge,
792			      &data, &data);
793
794	if (retval)
795		return retval;
796
797	if (data.data[0] != 0 && data.data[0] != 1)
798		return -ENODEV;
799
800	return data.data[0];
801}
802
803static int write_usb_charge(struct samsung_laptop *samsung,
804			    int enabled)
805{
806	const struct sabi_commands *commands = &samsung->config->commands;
807	struct sabi_data data;
808
809	memset(&data, 0, sizeof(data));
810	data.data[0] = 0x80 | enabled;
811	return sabi_command(samsung, commands->set_usb_charge,
812			    &data, NULL);
813}
814
815static ssize_t get_usb_charge(struct device *dev,
816			      struct device_attribute *attr,
817			      char *buf)
818{
819	struct samsung_laptop *samsung = dev_get_drvdata(dev);
820	int ret;
821
822	ret = read_usb_charge(samsung);
823	if (ret < 0)
824		return ret;
825
826	return sprintf(buf, "%d\n", ret);
827}
828
829static ssize_t set_usb_charge(struct device *dev,
830			      struct device_attribute *attr,
831			      const char *buf, size_t count)
832{
833	struct samsung_laptop *samsung = dev_get_drvdata(dev);
834	int ret, value;
835
836	if (!count || kstrtoint(buf, 0, &value) != 0)
837		return -EINVAL;
838
839	ret = write_usb_charge(samsung, !!value);
840	if (ret < 0)
841		return ret;
842
843	return count;
844}
845
846static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
847		   get_usb_charge, set_usb_charge);
848
849static int read_lid_handling(struct samsung_laptop *samsung)
850{
851	const struct sabi_commands *commands = &samsung->config->commands;
852	struct sabi_data data;
853	int retval;
854
855	if (commands->get_lid_handling == 0xFFFF)
856		return -ENODEV;
857
858	memset(&data, 0, sizeof(data));
859	retval = sabi_command(samsung, commands->get_lid_handling,
860			      &data, &data);
861
862	if (retval)
863		return retval;
864
865	return data.data[0] & 0x1;
866}
867
868static int write_lid_handling(struct samsung_laptop *samsung,
869			      int enabled)
870{
871	const struct sabi_commands *commands = &samsung->config->commands;
872	struct sabi_data data;
873
874	memset(&data, 0, sizeof(data));
875	data.data[0] = 0x80 | enabled;
876	return sabi_command(samsung, commands->set_lid_handling,
877			    &data, NULL);
878}
879
880static ssize_t get_lid_handling(struct device *dev,
881				struct device_attribute *attr,
882				char *buf)
883{
884	struct samsung_laptop *samsung = dev_get_drvdata(dev);
885	int ret;
886
887	ret = read_lid_handling(samsung);
888	if (ret < 0)
889		return ret;
890
891	return sprintf(buf, "%d\n", ret);
892}
893
894static ssize_t set_lid_handling(struct device *dev,
895				struct device_attribute *attr,
896				const char *buf, size_t count)
897{
898	struct samsung_laptop *samsung = dev_get_drvdata(dev);
899	int ret, value;
900
901	if (!count || kstrtoint(buf, 0, &value) != 0)
902		return -EINVAL;
903
904	ret = write_lid_handling(samsung, !!value);
905	if (ret < 0)
906		return ret;
907
908	return count;
909}
910
911static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO,
912		   get_lid_handling, set_lid_handling);
913
914static struct attribute *platform_attributes[] = {
915	&dev_attr_performance_level.attr,
916	&dev_attr_battery_life_extender.attr,
917	&dev_attr_usb_charge.attr,
918	&dev_attr_lid_handling.attr,
919	NULL
920};
921
922static int find_signature(void __iomem *memcheck, const char *testStr)
923{
924	int i = 0;
925	int loca;
926
927	for (loca = 0; loca < 0xffff; loca++) {
928		char temp = readb(memcheck + loca);
929
930		if (temp == testStr[i]) {
931			if (i == strlen(testStr)-1)
932				break;
933			++i;
934		} else {
935			i = 0;
936		}
937	}
938	return loca;
939}
940
941static void samsung_rfkill_exit(struct samsung_laptop *samsung)
942{
943	if (samsung->wlan.rfkill) {
944		rfkill_unregister(samsung->wlan.rfkill);
945		rfkill_destroy(samsung->wlan.rfkill);
946		samsung->wlan.rfkill = NULL;
947	}
948	if (samsung->bluetooth.rfkill) {
949		rfkill_unregister(samsung->bluetooth.rfkill);
950		rfkill_destroy(samsung->bluetooth.rfkill);
951		samsung->bluetooth.rfkill = NULL;
952	}
953}
954
955static int samsung_new_rfkill(struct samsung_laptop *samsung,
956			      struct samsung_rfkill *arfkill,
957			      const char *name, enum rfkill_type type,
958			      const struct rfkill_ops *ops,
959			      int blocked)
960{
961	struct rfkill **rfkill = &arfkill->rfkill;
962	int ret;
963
964	arfkill->type = type;
965	arfkill->samsung = samsung;
966
967	*rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
968			       type, ops, arfkill);
969
970	if (!*rfkill)
971		return -EINVAL;
972
973	if (blocked != -1)
974		rfkill_init_sw_state(*rfkill, blocked);
975
976	ret = rfkill_register(*rfkill);
977	if (ret) {
978		rfkill_destroy(*rfkill);
979		*rfkill = NULL;
980		return ret;
981	}
982	return 0;
983}
984
985static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
986{
987	return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
988				  RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
989}
990
991static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
992{
993	struct sabi_data data;
994	int ret;
995
996	ret = swsmi_wireless_status(samsung, &data);
997	if (ret) {
998		/* Some swsmi laptops use the old seclinux way to control
999		 * wireless devices */
1000		if (ret == -EINVAL)
1001			ret = samsung_rfkill_init_seclinux(samsung);
1002		return ret;
1003	}
1004
1005	/* 0x02 seems to mean that the device is no present/available */
1006
1007	if (data.data[WL_STATUS_WLAN] != 0x02)
1008		ret = samsung_new_rfkill(samsung, &samsung->wlan,
1009					 "samsung-wlan",
1010					 RFKILL_TYPE_WLAN,
1011					 &swsmi_rfkill_ops,
1012					 !data.data[WL_STATUS_WLAN]);
1013	if (ret)
1014		goto exit;
1015
1016	if (data.data[WL_STATUS_BT] != 0x02)
1017		ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
1018					 "samsung-bluetooth",
1019					 RFKILL_TYPE_BLUETOOTH,
1020					 &swsmi_rfkill_ops,
1021					 !data.data[WL_STATUS_BT]);
1022	if (ret)
1023		goto exit;
1024
1025exit:
1026	if (ret)
1027		samsung_rfkill_exit(samsung);
1028
1029	return ret;
1030}
1031
1032static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
1033{
1034	if (samsung->config->sabi_version == 2)
1035		return samsung_rfkill_init_seclinux(samsung);
1036	if (samsung->config->sabi_version == 3)
1037		return samsung_rfkill_init_swsmi(samsung);
1038	return 0;
1039}
1040
1041static void samsung_lid_handling_exit(struct samsung_laptop *samsung)
1042{
1043	if (samsung->quirks->lid_handling)
1044		write_lid_handling(samsung, 0);
1045}
1046
1047static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
1048{
1049	int retval = 0;
1050
1051	if (samsung->quirks->lid_handling)
1052		retval = write_lid_handling(samsung, 1);
1053
1054	return retval;
1055}
1056
1057static int kbd_backlight_enable(struct samsung_laptop *samsung)
1058{
1059	const struct sabi_commands *commands = &samsung->config->commands;
1060	struct sabi_data data;
1061	int retval;
1062
1063	if (commands->kbd_backlight == 0xFFFF)
1064		return -ENODEV;
1065
1066	memset(&data, 0, sizeof(data));
1067	data.d0 = 0xaabb;
1068	retval = sabi_command(samsung, commands->kbd_backlight,
1069			      &data, &data);
1070
1071	if (retval)
1072		return retval;
1073
1074	if (data.d0 != 0xccdd)
1075		return -ENODEV;
1076	return 0;
1077}
1078
1079static int kbd_backlight_read(struct samsung_laptop *samsung)
1080{
1081	const struct sabi_commands *commands = &samsung->config->commands;
1082	struct sabi_data data;
1083	int retval;
1084
1085	memset(&data, 0, sizeof(data));
1086	data.data[0] = 0x81;
1087	retval = sabi_command(samsung, commands->kbd_backlight,
1088			      &data, &data);
1089
1090	if (retval)
1091		return retval;
1092
1093	return data.data[0];
1094}
1095
1096static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
1097{
1098	const struct sabi_commands *commands = &samsung->config->commands;
1099	struct sabi_data data;
1100
1101	memset(&data, 0, sizeof(data));
1102	data.d0 = 0x82 | ((brightness & 0xFF) << 8);
1103	return sabi_command(samsung, commands->kbd_backlight,
1104			    &data, NULL);
1105}
1106
1107static void kbd_led_update(struct work_struct *work)
1108{
1109	struct samsung_laptop *samsung;
1110
1111	samsung = container_of(work, struct samsung_laptop, kbd_led_work);
1112	kbd_backlight_write(samsung, samsung->kbd_led_wk);
1113}
1114
1115static void kbd_led_set(struct led_classdev *led_cdev,
1116			enum led_brightness value)
1117{
1118	struct samsung_laptop *samsung;
1119
1120	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1121
1122	if (value > samsung->kbd_led.max_brightness)
1123		value = samsung->kbd_led.max_brightness;
1124
1125	samsung->kbd_led_wk = value;
1126	queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
1127}
1128
1129static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
1130{
1131	struct samsung_laptop *samsung;
1132
1133	samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
1134	return kbd_backlight_read(samsung);
1135}
1136
1137static void samsung_leds_exit(struct samsung_laptop *samsung)
1138{
1139	led_classdev_unregister(&samsung->kbd_led);
1140	if (samsung->led_workqueue)
1141		destroy_workqueue(samsung->led_workqueue);
1142}
1143
1144static int __init samsung_leds_init(struct samsung_laptop *samsung)
1145{
1146	int ret = 0;
1147
1148	samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
1149	if (!samsung->led_workqueue)
1150		return -ENOMEM;
1151
1152	if (kbd_backlight_enable(samsung) >= 0) {
1153		INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
1154
1155		samsung->kbd_led.name = "samsung::kbd_backlight";
1156		samsung->kbd_led.brightness_set = kbd_led_set;
1157		samsung->kbd_led.brightness_get = kbd_led_get;
1158		samsung->kbd_led.max_brightness = 8;
1159		if (samsung->quirks->four_kbd_backlight_levels)
1160			samsung->kbd_led.max_brightness = 4;
1161
1162		ret = led_classdev_register(&samsung->platform_device->dev,
1163					   &samsung->kbd_led);
1164	}
1165
1166	if (ret)
1167		samsung_leds_exit(samsung);
1168
1169	return ret;
1170}
1171
1172static void samsung_backlight_exit(struct samsung_laptop *samsung)
1173{
1174	if (samsung->backlight_device) {
1175		backlight_device_unregister(samsung->backlight_device);
1176		samsung->backlight_device = NULL;
1177	}
1178}
1179
1180static int __init samsung_backlight_init(struct samsung_laptop *samsung)
1181{
1182	struct backlight_device *bd;
1183	struct backlight_properties props;
1184
1185	if (!samsung->handle_backlight)
1186		return 0;
1187
1188	memset(&props, 0, sizeof(struct backlight_properties));
1189	props.type = BACKLIGHT_PLATFORM;
1190	props.max_brightness = samsung->config->max_brightness -
1191		samsung->config->min_brightness;
1192
1193	bd = backlight_device_register("samsung",
1194				       &samsung->platform_device->dev,
1195				       samsung, &backlight_ops,
1196				       &props);
1197	if (IS_ERR(bd))
1198		return PTR_ERR(bd);
1199
1200	samsung->backlight_device = bd;
1201	samsung->backlight_device->props.brightness = read_brightness(samsung);
1202	samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
1203	backlight_update_status(samsung->backlight_device);
1204
1205	return 0;
1206}
1207
1208static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
1209					struct attribute *attr, int idx)
1210{
1211	struct device *dev = container_of(kobj, struct device, kobj);
1212	struct samsung_laptop *samsung = dev_get_drvdata(dev);
1213	bool ok = true;
1214
1215	if (attr == &dev_attr_performance_level.attr)
1216		ok = !!samsung->config->performance_levels[0].name;
1217	if (attr == &dev_attr_battery_life_extender.attr)
1218		ok = !!(read_battery_life_extender(samsung) >= 0);
1219	if (attr == &dev_attr_usb_charge.attr)
1220		ok = !!(read_usb_charge(samsung) >= 0);
1221	if (attr == &dev_attr_lid_handling.attr)
1222		ok = !!(read_lid_handling(samsung) >= 0);
1223
1224	return ok ? attr->mode : 0;
1225}
1226
1227static const struct attribute_group platform_attribute_group = {
1228	.is_visible = samsung_sysfs_is_visible,
1229	.attrs = platform_attributes
1230};
1231
1232static void samsung_sysfs_exit(struct samsung_laptop *samsung)
1233{
1234	struct platform_device *device = samsung->platform_device;
1235
1236	sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
1237}
1238
1239static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
1240{
1241	struct platform_device *device = samsung->platform_device;
1242
1243	return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
1244
1245}
1246
1247static int samsung_laptop_call_show(struct seq_file *m, void *data)
1248{
1249	struct samsung_laptop *samsung = m->private;
1250	struct sabi_data *sdata = &samsung->debug.data;
1251	int ret;
1252
1253	seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1254		   samsung->debug.command,
1255		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1256
1257	ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
1258
1259	if (ret) {
1260		seq_printf(m, "SABI command 0x%04x failed\n",
1261			   samsung->debug.command);
1262		return ret;
1263	}
1264
1265	seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
1266		   sdata->d0, sdata->d1, sdata->d2, sdata->d3);
1267	return 0;
1268}
1269DEFINE_SHOW_ATTRIBUTE(samsung_laptop_call);
1270
1271static void samsung_debugfs_exit(struct samsung_laptop *samsung)
1272{
1273	debugfs_remove_recursive(samsung->debug.root);
1274}
1275
1276static void samsung_debugfs_init(struct samsung_laptop *samsung)
1277{
1278	struct dentry *root;
1279
1280	root = debugfs_create_dir("samsung-laptop", NULL);
1281	samsung->debug.root = root;
1282
1283	samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
1284	samsung->debug.f0000_wrapper.size = 0xffff;
1285
1286	samsung->debug.data_wrapper.data = &samsung->debug.data;
1287	samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
1288
1289	samsung->debug.sdiag_wrapper.data = samsung->sdiag;
1290	samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
1291
1292	debugfs_create_u16("command", S_IRUGO | S_IWUSR, root,
1293			   &samsung->debug.command);
1294	debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root,
1295			   &samsung->debug.data.d0);
1296	debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root,
1297			   &samsung->debug.data.d1);
1298	debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root,
1299			   &samsung->debug.data.d2);
1300	debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root,
1301			  &samsung->debug.data.d3);
1302	debugfs_create_blob("data", S_IRUGO | S_IWUSR, root,
1303			    &samsung->debug.data_wrapper);
1304	debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root,
1305			    &samsung->debug.f0000_wrapper);
1306	debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung,
1307			    &samsung_laptop_call_fops);
1308	debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root,
1309			    &samsung->debug.sdiag_wrapper);
1310}
1311
1312static void samsung_sabi_exit(struct samsung_laptop *samsung)
1313{
1314	const struct sabi_config *config = samsung->config;
1315
1316	/* Turn off "Linux" mode in the BIOS */
1317	if (config && config->commands.set_linux != 0xff)
1318		sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
1319
1320	if (samsung->sabi_iface) {
1321		iounmap(samsung->sabi_iface);
1322		samsung->sabi_iface = NULL;
1323	}
1324	if (samsung->f0000_segment) {
1325		iounmap(samsung->f0000_segment);
1326		samsung->f0000_segment = NULL;
1327	}
1328
1329	samsung->config = NULL;
1330}
1331
1332static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
1333				      unsigned int ifaceP)
1334{
1335	const struct sabi_config *config = samsung->config;
1336
1337	printk(KERN_DEBUG "This computer supports SABI==%x\n",
1338	       loca + 0xf0000 - 6);
1339
1340	printk(KERN_DEBUG "SABI header:\n");
1341	printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
1342	       readw(samsung->sabi + config->header_offsets.port));
1343	printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
1344	       readb(samsung->sabi + config->header_offsets.iface_func));
1345	printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
1346	       readb(samsung->sabi + config->header_offsets.en_mem));
1347	printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
1348	       readb(samsung->sabi + config->header_offsets.re_mem));
1349	printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
1350	       readw(samsung->sabi + config->header_offsets.data_offset));
1351	printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
1352	       readw(samsung->sabi + config->header_offsets.data_segment));
1353
1354	printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
1355}
1356
1357static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
1358{
1359	int loca = find_signature(samsung->f0000_segment, "SDiaG@");
1360	int i;
1361
1362	if (loca == 0xffff)
1363		return ;
1364
1365	/* Example:
1366	 * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
1367	 *
1368	 * Product name: 90X3A
1369	 * BIOS Version: 07HL
1370	 */
1371	loca += 1;
1372	for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
1373		char temp = readb(samsung->f0000_segment + loca);
1374
1375		if (isalnum(temp) || temp == '/' || temp == '-')
1376			samsung->sdiag[i++] = temp;
1377		else
1378			break ;
1379	}
1380
1381	if (debug && samsung->sdiag[0])
1382		pr_info("sdiag: %s", samsung->sdiag);
1383}
1384
1385static int __init samsung_sabi_init(struct samsung_laptop *samsung)
1386{
1387	const struct sabi_config *config = NULL;
1388	const struct sabi_commands *commands;
1389	unsigned int ifaceP;
1390	int loca = 0xffff;
1391	int ret = 0;
1392	int i;
1393
1394	samsung->f0000_segment = ioremap(0xf0000, 0xffff);
1395	if (!samsung->f0000_segment) {
1396		if (debug || force)
1397			pr_err("Can't map the segment at 0xf0000\n");
1398		ret = -EINVAL;
1399		goto exit;
1400	}
1401
1402	samsung_sabi_diag(samsung);
1403
1404	/* Try to find one of the signatures in memory to find the header */
1405	for (i = 0; sabi_configs[i].test_string != NULL; ++i) {
1406		samsung->config = &sabi_configs[i];
1407		loca = find_signature(samsung->f0000_segment,
1408				      samsung->config->test_string);
1409		if (loca != 0xffff)
1410			break;
1411	}
1412
1413	if (loca == 0xffff) {
1414		if (debug || force)
1415			pr_err("This computer does not support SABI\n");
1416		ret = -ENODEV;
1417		goto exit;
1418	}
1419
1420	config = samsung->config;
1421	commands = &config->commands;
1422
1423	/* point to the SMI port Number */
1424	loca += 1;
1425	samsung->sabi = (samsung->f0000_segment + loca);
1426
1427	/* Get a pointer to the SABI Interface */
1428	ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
1429	ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
1430
1431	if (debug)
1432		samsung_sabi_infos(samsung, loca, ifaceP);
1433
1434	samsung->sabi_iface = ioremap(ifaceP, 16);
1435	if (!samsung->sabi_iface) {
1436		pr_err("Can't remap %x\n", ifaceP);
1437		ret = -EINVAL;
1438		goto exit;
1439	}
1440
1441	/* Turn on "Linux" mode in the BIOS */
1442	if (commands->set_linux != 0xff) {
1443		int retval = sabi_set_commandb(samsung,
1444					       commands->set_linux, 0x81);
1445		if (retval) {
1446			pr_warn("Linux mode was not set!\n");
1447			ret = -ENODEV;
1448			goto exit;
1449		}
1450	}
1451
1452	/* Check for stepping quirk */
1453	if (samsung->handle_backlight)
1454		check_for_stepping_quirk(samsung);
1455
1456	pr_info("detected SABI interface: %s\n",
1457		samsung->config->test_string);
1458
1459exit:
1460	if (ret)
1461		samsung_sabi_exit(samsung);
1462
1463	return ret;
1464}
1465
1466static void samsung_platform_exit(struct samsung_laptop *samsung)
1467{
1468	if (samsung->platform_device) {
1469		platform_device_unregister(samsung->platform_device);
1470		samsung->platform_device = NULL;
1471	}
1472}
1473
1474static int samsung_pm_notification(struct notifier_block *nb,
1475				   unsigned long val, void *ptr)
1476{
1477	struct samsung_laptop *samsung;
1478
1479	samsung = container_of(nb, struct samsung_laptop, pm_nb);
1480	if (val == PM_POST_HIBERNATION &&
1481	    samsung->quirks->enable_kbd_backlight)
1482		kbd_backlight_enable(samsung);
1483
1484	if (val == PM_POST_HIBERNATION && samsung->quirks->lid_handling)
1485		write_lid_handling(samsung, 1);
1486
1487	return 0;
1488}
1489
1490static int __init samsung_platform_init(struct samsung_laptop *samsung)
1491{
1492	struct platform_device *pdev;
1493
1494	pdev = platform_device_register_simple("samsung", -1, NULL, 0);
1495	if (IS_ERR(pdev))
1496		return PTR_ERR(pdev);
1497
1498	samsung->platform_device = pdev;
1499	platform_set_drvdata(samsung->platform_device, samsung);
1500	return 0;
1501}
1502
1503static struct samsung_quirks *quirks;
1504
1505static int __init samsung_dmi_matched(const struct dmi_system_id *d)
1506{
1507	quirks = d->driver_data;
1508	return 0;
1509}
1510
1511static const struct dmi_system_id samsung_dmi_table[] __initconst = {
1512	{
1513		.matches = {
1514			DMI_MATCH(DMI_SYS_VENDOR,
1515					"SAMSUNG ELECTRONICS CO., LTD."),
1516			DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
1517		},
1518	},
1519	{
1520		.matches = {
1521			DMI_MATCH(DMI_SYS_VENDOR,
1522					"SAMSUNG ELECTRONICS CO., LTD."),
1523			DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
1524		},
1525	},
1526	{
1527		.matches = {
1528			DMI_MATCH(DMI_SYS_VENDOR,
1529					"SAMSUNG ELECTRONICS CO., LTD."),
1530			DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
1531		},
1532	},
1533	{
1534		.matches = {
1535			DMI_MATCH(DMI_SYS_VENDOR,
1536					"SAMSUNG ELECTRONICS CO., LTD."),
1537			DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
1538		},
1539	},
1540	/* DMI ids for laptops with bad Chassis Type */
1541	{
1542	  .ident = "R40/R41",
1543	  .matches = {
1544		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1545		DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
1546		DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
1547		},
1548	},
1549	/* Specific DMI ids for laptop with quirks */
1550	{
1551	 .callback = samsung_dmi_matched,
1552	 .ident = "N150P",
1553	 .matches = {
1554		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1555		DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
1556		DMI_MATCH(DMI_BOARD_NAME, "N150P"),
1557		},
1558	 .driver_data = &samsung_use_native_backlight,
1559	},
1560	{
1561	 .callback = samsung_dmi_matched,
1562	 .ident = "N145P/N250P/N260P",
1563	 .matches = {
1564		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1565		DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
1566		DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
1567		},
1568	 .driver_data = &samsung_use_native_backlight,
1569	},
1570	{
1571	 .callback = samsung_dmi_matched,
1572	 .ident = "N150/N210/N220",
1573	 .matches = {
1574		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1575		DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
1576		DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
1577		},
1578	 .driver_data = &samsung_broken_acpi_video,
1579	},
1580	{
1581	 .callback = samsung_dmi_matched,
1582	 .ident = "NF110/NF210/NF310",
1583	 .matches = {
1584		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1585		DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
1586		DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
1587		},
1588	 .driver_data = &samsung_broken_acpi_video,
1589	},
1590	{
1591	 .callback = samsung_dmi_matched,
1592	 .ident = "X360",
1593	 .matches = {
1594		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1595		DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
1596		DMI_MATCH(DMI_BOARD_NAME, "X360"),
1597		},
1598	 .driver_data = &samsung_broken_acpi_video,
1599	},
1600	{
1601	 .callback = samsung_dmi_matched,
1602	 .ident = "N250P",
1603	 .matches = {
1604		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1605		DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
1606		DMI_MATCH(DMI_BOARD_NAME, "N250P"),
1607		},
1608	 .driver_data = &samsung_use_native_backlight,
1609	},
1610	{
1611	 .callback = samsung_dmi_matched,
1612	 .ident = "NC210",
1613	 .matches = {
1614		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1615		DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
1616		DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
1617		},
1618	 .driver_data = &samsung_broken_acpi_video,
1619	},
1620	{
1621	 .callback = samsung_dmi_matched,
1622	 .ident = "730U3E/740U3E",
1623	 .matches = {
1624		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1625		DMI_MATCH(DMI_PRODUCT_NAME, "730U3E/740U3E"),
1626		},
1627	 .driver_data = &samsung_np740u3e,
1628	},
1629	{
1630	 .callback = samsung_dmi_matched,
1631	 .ident = "300V3Z/300V4Z/300V5Z",
1632	 .matches = {
1633		DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
1634		DMI_MATCH(DMI_PRODUCT_NAME, "300V3Z/300V4Z/300V5Z"),
1635		},
1636	 .driver_data = &samsung_lid_handling,
1637	},
1638	{ },
1639};
1640MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
1641
1642static struct platform_device *samsung_platform_device;
1643
1644static int __init samsung_init(void)
1645{
1646	struct samsung_laptop *samsung;
1647	int ret;
1648
1649	if (efi_enabled(EFI_BOOT))
1650		return -ENODEV;
1651
1652	quirks = &samsung_unknown;
1653	if (!force && !dmi_check_system(samsung_dmi_table))
1654		return -ENODEV;
1655
1656	samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
1657	if (!samsung)
1658		return -ENOMEM;
1659
1660	mutex_init(&samsung->sabi_mutex);
1661	samsung->handle_backlight = true;
1662	samsung->quirks = quirks;
1663
1664#ifdef CONFIG_ACPI
1665	if (samsung->quirks->broken_acpi_video)
1666		acpi_video_set_dmi_backlight_type(acpi_backlight_vendor);
1667	if (samsung->quirks->use_native_backlight)
1668		acpi_video_set_dmi_backlight_type(acpi_backlight_native);
1669
1670	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
1671		samsung->handle_backlight = false;
1672#endif
1673
1674	ret = samsung_platform_init(samsung);
1675	if (ret)
1676		goto error_platform;
1677
1678	ret = samsung_sabi_init(samsung);
1679	if (ret)
1680		goto error_sabi;
1681
1682	ret = samsung_sysfs_init(samsung);
1683	if (ret)
1684		goto error_sysfs;
1685
1686	ret = samsung_backlight_init(samsung);
1687	if (ret)
1688		goto error_backlight;
1689
1690	ret = samsung_rfkill_init(samsung);
1691	if (ret)
1692		goto error_rfkill;
1693
1694	ret = samsung_leds_init(samsung);
1695	if (ret)
1696		goto error_leds;
1697
1698	ret = samsung_lid_handling_init(samsung);
1699	if (ret)
1700		goto error_lid_handling;
1701
1702	samsung_debugfs_init(samsung);
1703
1704	samsung->pm_nb.notifier_call = samsung_pm_notification;
1705	register_pm_notifier(&samsung->pm_nb);
1706
1707	samsung_platform_device = samsung->platform_device;
1708	return ret;
1709
1710error_lid_handling:
1711	samsung_leds_exit(samsung);
1712error_leds:
1713	samsung_rfkill_exit(samsung);
1714error_rfkill:
1715	samsung_backlight_exit(samsung);
1716error_backlight:
1717	samsung_sysfs_exit(samsung);
1718error_sysfs:
1719	samsung_sabi_exit(samsung);
1720error_sabi:
1721	samsung_platform_exit(samsung);
1722error_platform:
1723	kfree(samsung);
1724	return ret;
1725}
1726
1727static void __exit samsung_exit(void)
1728{
1729	struct samsung_laptop *samsung;
1730
1731	samsung = platform_get_drvdata(samsung_platform_device);
1732	unregister_pm_notifier(&samsung->pm_nb);
1733
1734	samsung_debugfs_exit(samsung);
1735	samsung_lid_handling_exit(samsung);
1736	samsung_leds_exit(samsung);
1737	samsung_rfkill_exit(samsung);
1738	samsung_backlight_exit(samsung);
1739	samsung_sysfs_exit(samsung);
1740	samsung_sabi_exit(samsung);
1741	samsung_platform_exit(samsung);
1742
1743	kfree(samsung);
1744	samsung_platform_device = NULL;
1745}
1746
1747module_init(samsung_init);
1748module_exit(samsung_exit);
1749
1750MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
1751MODULE_DESCRIPTION("Samsung Backlight driver");
1752MODULE_LICENSE("GPL");
1753