xref: /linux/drivers/platform/x86/samsung-laptop.c (revision b43ab901d671e3e3cad425ea5e9a3c74e266dcdd)
1 /*
2  * Samsung Laptop driver
3  *
4  * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
5  * Copyright (C) 2009,2011 Novell Inc.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 as published by
9  * the Free Software Foundation.
10  *
11  */
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13 
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <linux/module.h>
17 #include <linux/delay.h>
18 #include <linux/pci.h>
19 #include <linux/backlight.h>
20 #include <linux/fb.h>
21 #include <linux/dmi.h>
22 #include <linux/platform_device.h>
23 #include <linux/rfkill.h>
24 
25 /*
26  * This driver is needed because a number of Samsung laptops do not hook
27  * their control settings through ACPI.  So we have to poke around in the
28  * BIOS to do things like brightness values, and "special" key controls.
29  */
30 
31 /*
32  * We have 0 - 8 as valid brightness levels.  The specs say that level 0 should
33  * be reserved by the BIOS (which really doesn't make much sense), we tell
34  * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
35  */
36 #define MAX_BRIGHT	0x07
37 
38 
39 #define SABI_IFACE_MAIN			0x00
40 #define SABI_IFACE_SUB			0x02
41 #define SABI_IFACE_COMPLETE		0x04
42 #define SABI_IFACE_DATA			0x05
43 
44 /* Structure to get data back to the calling function */
45 struct sabi_retval {
46 	u8 retval[20];
47 };
48 
49 struct sabi_header_offsets {
50 	u8 port;
51 	u8 re_mem;
52 	u8 iface_func;
53 	u8 en_mem;
54 	u8 data_offset;
55 	u8 data_segment;
56 };
57 
58 struct sabi_commands {
59 	/*
60 	 * Brightness is 0 - 8, as described above.
61 	 * Value 0 is for the BIOS to use
62 	 */
63 	u8 get_brightness;
64 	u8 set_brightness;
65 
66 	/*
67 	 * first byte:
68 	 * 0x00 - wireless is off
69 	 * 0x01 - wireless is on
70 	 * second byte:
71 	 * 0x02 - 3G is off
72 	 * 0x03 - 3G is on
73 	 * TODO, verify 3G is correct, that doesn't seem right...
74 	 */
75 	u8 get_wireless_button;
76 	u8 set_wireless_button;
77 
78 	/* 0 is off, 1 is on */
79 	u8 get_backlight;
80 	u8 set_backlight;
81 
82 	/*
83 	 * 0x80 or 0x00 - no action
84 	 * 0x81 - recovery key pressed
85 	 */
86 	u8 get_recovery_mode;
87 	u8 set_recovery_mode;
88 
89 	/*
90 	 * on seclinux: 0 is low, 1 is high,
91 	 * on swsmi: 0 is normal, 1 is silent, 2 is turbo
92 	 */
93 	u8 get_performance_level;
94 	u8 set_performance_level;
95 
96 	/*
97 	 * Tell the BIOS that Linux is running on this machine.
98 	 * 81 is on, 80 is off
99 	 */
100 	u8 set_linux;
101 };
102 
103 struct sabi_performance_level {
104 	const char *name;
105 	u8 value;
106 };
107 
108 struct sabi_config {
109 	const char *test_string;
110 	u16 main_function;
111 	const struct sabi_header_offsets header_offsets;
112 	const struct sabi_commands commands;
113 	const struct sabi_performance_level performance_levels[4];
114 	u8 min_brightness;
115 	u8 max_brightness;
116 };
117 
118 static const struct sabi_config sabi_configs[] = {
119 	{
120 		.test_string = "SECLINUX",
121 
122 		.main_function = 0x4c49,
123 
124 		.header_offsets = {
125 			.port = 0x00,
126 			.re_mem = 0x02,
127 			.iface_func = 0x03,
128 			.en_mem = 0x04,
129 			.data_offset = 0x05,
130 			.data_segment = 0x07,
131 		},
132 
133 		.commands = {
134 			.get_brightness = 0x00,
135 			.set_brightness = 0x01,
136 
137 			.get_wireless_button = 0x02,
138 			.set_wireless_button = 0x03,
139 
140 			.get_backlight = 0x04,
141 			.set_backlight = 0x05,
142 
143 			.get_recovery_mode = 0x06,
144 			.set_recovery_mode = 0x07,
145 
146 			.get_performance_level = 0x08,
147 			.set_performance_level = 0x09,
148 
149 			.set_linux = 0x0a,
150 		},
151 
152 		.performance_levels = {
153 			{
154 				.name = "silent",
155 				.value = 0,
156 			},
157 			{
158 				.name = "normal",
159 				.value = 1,
160 			},
161 			{ },
162 		},
163 		.min_brightness = 1,
164 		.max_brightness = 8,
165 	},
166 	{
167 		.test_string = "SwSmi@",
168 
169 		.main_function = 0x5843,
170 
171 		.header_offsets = {
172 			.port = 0x00,
173 			.re_mem = 0x04,
174 			.iface_func = 0x02,
175 			.en_mem = 0x03,
176 			.data_offset = 0x05,
177 			.data_segment = 0x07,
178 		},
179 
180 		.commands = {
181 			.get_brightness = 0x10,
182 			.set_brightness = 0x11,
183 
184 			.get_wireless_button = 0x12,
185 			.set_wireless_button = 0x13,
186 
187 			.get_backlight = 0x2d,
188 			.set_backlight = 0x2e,
189 
190 			.get_recovery_mode = 0xff,
191 			.set_recovery_mode = 0xff,
192 
193 			.get_performance_level = 0x31,
194 			.set_performance_level = 0x32,
195 
196 			.set_linux = 0xff,
197 		},
198 
199 		.performance_levels = {
200 			{
201 				.name = "normal",
202 				.value = 0,
203 			},
204 			{
205 				.name = "silent",
206 				.value = 1,
207 			},
208 			{
209 				.name = "overclock",
210 				.value = 2,
211 			},
212 			{ },
213 		},
214 		.min_brightness = 0,
215 		.max_brightness = 8,
216 	},
217 	{ },
218 };
219 
220 static const struct sabi_config *sabi_config;
221 
222 static void __iomem *sabi;
223 static void __iomem *sabi_iface;
224 static void __iomem *f0000_segment;
225 static struct backlight_device *backlight_device;
226 static struct mutex sabi_mutex;
227 static struct platform_device *sdev;
228 static struct rfkill *rfk;
229 static bool has_stepping_quirk;
230 
231 static bool force;
232 module_param(force, bool, 0);
233 MODULE_PARM_DESC(force,
234 		"Disable the DMI check and forces the driver to be loaded");
235 
236 static bool debug;
237 module_param(debug, bool, S_IRUGO | S_IWUSR);
238 MODULE_PARM_DESC(debug, "Debug enabled or not");
239 
240 static int sabi_get_command(u8 command, struct sabi_retval *sretval)
241 {
242 	int retval = 0;
243 	u16 port = readw(sabi + sabi_config->header_offsets.port);
244 	u8 complete, iface_data;
245 
246 	mutex_lock(&sabi_mutex);
247 
248 	/* enable memory to be able to write to it */
249 	outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
250 
251 	/* write out the command */
252 	writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
253 	writew(command, sabi_iface + SABI_IFACE_SUB);
254 	writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
255 	outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
256 
257 	/* write protect memory to make it safe */
258 	outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
259 
260 	/* see if the command actually succeeded */
261 	complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
262 	iface_data = readb(sabi_iface + SABI_IFACE_DATA);
263 	if (complete != 0xaa || iface_data == 0xff) {
264 		pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
265 		        command, complete, iface_data);
266 		retval = -EINVAL;
267 		goto exit;
268 	}
269 	/*
270 	 * Save off the data into a structure so the caller use it.
271 	 * Right now we only want the first 4 bytes,
272 	 * There are commands that need more, but not for the ones we
273 	 * currently care about.
274 	 */
275 	sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA);
276 	sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1);
277 	sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2);
278 	sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3);
279 
280 exit:
281 	mutex_unlock(&sabi_mutex);
282 	return retval;
283 
284 }
285 
286 static int sabi_set_command(u8 command, u8 data)
287 {
288 	int retval = 0;
289 	u16 port = readw(sabi + sabi_config->header_offsets.port);
290 	u8 complete, iface_data;
291 
292 	mutex_lock(&sabi_mutex);
293 
294 	/* enable memory to be able to write to it */
295 	outb(readb(sabi + sabi_config->header_offsets.en_mem), port);
296 
297 	/* write out the command */
298 	writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN);
299 	writew(command, sabi_iface + SABI_IFACE_SUB);
300 	writeb(0, sabi_iface + SABI_IFACE_COMPLETE);
301 	writeb(data, sabi_iface + SABI_IFACE_DATA);
302 	outb(readb(sabi + sabi_config->header_offsets.iface_func), port);
303 
304 	/* write protect memory to make it safe */
305 	outb(readb(sabi + sabi_config->header_offsets.re_mem), port);
306 
307 	/* see if the command actually succeeded */
308 	complete = readb(sabi_iface + SABI_IFACE_COMPLETE);
309 	iface_data = readb(sabi_iface + SABI_IFACE_DATA);
310 	if (complete != 0xaa || iface_data == 0xff) {
311 		pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n",
312 		       command, complete, iface_data);
313 		retval = -EINVAL;
314 	}
315 
316 	mutex_unlock(&sabi_mutex);
317 	return retval;
318 }
319 
320 static void test_backlight(void)
321 {
322 	struct sabi_retval sretval;
323 
324 	sabi_get_command(sabi_config->commands.get_backlight, &sretval);
325 	printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
326 
327 	sabi_set_command(sabi_config->commands.set_backlight, 0);
328 	printk(KERN_DEBUG "backlight should be off\n");
329 
330 	sabi_get_command(sabi_config->commands.get_backlight, &sretval);
331 	printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
332 
333 	msleep(1000);
334 
335 	sabi_set_command(sabi_config->commands.set_backlight, 1);
336 	printk(KERN_DEBUG "backlight should be on\n");
337 
338 	sabi_get_command(sabi_config->commands.get_backlight, &sretval);
339 	printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]);
340 }
341 
342 static void test_wireless(void)
343 {
344 	struct sabi_retval sretval;
345 
346 	sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
347 	printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
348 
349 	sabi_set_command(sabi_config->commands.set_wireless_button, 0);
350 	printk(KERN_DEBUG "wireless led should be off\n");
351 
352 	sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
353 	printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
354 
355 	msleep(1000);
356 
357 	sabi_set_command(sabi_config->commands.set_wireless_button, 1);
358 	printk(KERN_DEBUG "wireless led should be on\n");
359 
360 	sabi_get_command(sabi_config->commands.get_wireless_button, &sretval);
361 	printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]);
362 }
363 
364 static u8 read_brightness(void)
365 {
366 	struct sabi_retval sretval;
367 	int user_brightness = 0;
368 	int retval;
369 
370 	retval = sabi_get_command(sabi_config->commands.get_brightness,
371 				  &sretval);
372 	if (!retval) {
373 		user_brightness = sretval.retval[0];
374 		if (user_brightness > sabi_config->min_brightness)
375 			user_brightness -= sabi_config->min_brightness;
376 		else
377 			user_brightness = 0;
378 	}
379 	return user_brightness;
380 }
381 
382 static void set_brightness(u8 user_brightness)
383 {
384 	u8 user_level = user_brightness + sabi_config->min_brightness;
385 
386 	if (has_stepping_quirk && user_level != 0) {
387 		/*
388 		 * short circuit if the specified level is what's already set
389 		 * to prevent the screen from flickering needlessly
390 		 */
391 		if (user_brightness == read_brightness())
392 			return;
393 
394 		sabi_set_command(sabi_config->commands.set_brightness, 0);
395 	}
396 
397 	sabi_set_command(sabi_config->commands.set_brightness, user_level);
398 }
399 
400 static int get_brightness(struct backlight_device *bd)
401 {
402 	return (int)read_brightness();
403 }
404 
405 static void check_for_stepping_quirk(void)
406 {
407 	u8 initial_level;
408 	u8 check_level;
409 	u8 orig_level = read_brightness();
410 
411 	/*
412 	 * Some laptops exhibit the strange behaviour of stepping toward
413 	 * (rather than setting) the brightness except when changing to/from
414 	 * brightness level 0. This behaviour is checked for here and worked
415 	 * around in set_brightness.
416 	 */
417 
418 	if (orig_level == 0)
419 		set_brightness(1);
420 
421 	initial_level = read_brightness();
422 
423 	if (initial_level <= 2)
424 		check_level = initial_level + 2;
425 	else
426 		check_level = initial_level - 2;
427 
428 	has_stepping_quirk = false;
429 	set_brightness(check_level);
430 
431 	if (read_brightness() != check_level) {
432 		has_stepping_quirk = true;
433 		pr_info("enabled workaround for brightness stepping quirk\n");
434 	}
435 
436 	set_brightness(orig_level);
437 }
438 
439 static int update_status(struct backlight_device *bd)
440 {
441 	set_brightness(bd->props.brightness);
442 
443 	if (bd->props.power == FB_BLANK_UNBLANK)
444 		sabi_set_command(sabi_config->commands.set_backlight, 1);
445 	else
446 		sabi_set_command(sabi_config->commands.set_backlight, 0);
447 	return 0;
448 }
449 
450 static const struct backlight_ops backlight_ops = {
451 	.get_brightness	= get_brightness,
452 	.update_status	= update_status,
453 };
454 
455 static int rfkill_set(void *data, bool blocked)
456 {
457 	/* Do something with blocked...*/
458 	/*
459 	 * blocked == false is on
460 	 * blocked == true is off
461 	 */
462 	if (blocked)
463 		sabi_set_command(sabi_config->commands.set_wireless_button, 0);
464 	else
465 		sabi_set_command(sabi_config->commands.set_wireless_button, 1);
466 
467 	return 0;
468 }
469 
470 static struct rfkill_ops rfkill_ops = {
471 	.set_block = rfkill_set,
472 };
473 
474 static int init_wireless(struct platform_device *sdev)
475 {
476 	int retval;
477 
478 	rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN,
479 			   &rfkill_ops, NULL);
480 	if (!rfk)
481 		return -ENOMEM;
482 
483 	retval = rfkill_register(rfk);
484 	if (retval) {
485 		rfkill_destroy(rfk);
486 		return -ENODEV;
487 	}
488 
489 	return 0;
490 }
491 
492 static void destroy_wireless(void)
493 {
494 	rfkill_unregister(rfk);
495 	rfkill_destroy(rfk);
496 }
497 
498 static ssize_t get_performance_level(struct device *dev,
499 				     struct device_attribute *attr, char *buf)
500 {
501 	struct sabi_retval sretval;
502 	int retval;
503 	int i;
504 
505 	/* Read the state */
506 	retval = sabi_get_command(sabi_config->commands.get_performance_level,
507 				  &sretval);
508 	if (retval)
509 		return retval;
510 
511 	/* The logic is backwards, yeah, lots of fun... */
512 	for (i = 0; sabi_config->performance_levels[i].name; ++i) {
513 		if (sretval.retval[0] == sabi_config->performance_levels[i].value)
514 			return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name);
515 	}
516 	return sprintf(buf, "%s\n", "unknown");
517 }
518 
519 static ssize_t set_performance_level(struct device *dev,
520 				struct device_attribute *attr, const char *buf,
521 				size_t count)
522 {
523 	if (count >= 1) {
524 		int i;
525 		for (i = 0; sabi_config->performance_levels[i].name; ++i) {
526 			const struct sabi_performance_level *level =
527 				&sabi_config->performance_levels[i];
528 			if (!strncasecmp(level->name, buf, strlen(level->name))) {
529 				sabi_set_command(sabi_config->commands.set_performance_level,
530 						 level->value);
531 				break;
532 			}
533 		}
534 		if (!sabi_config->performance_levels[i].name)
535 			return -EINVAL;
536 	}
537 	return count;
538 }
539 static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
540 		   get_performance_level, set_performance_level);
541 
542 
543 static int __init dmi_check_cb(const struct dmi_system_id *id)
544 {
545 	pr_info("found laptop model '%s'\n",
546 		id->ident);
547 	return 1;
548 }
549 
550 static struct dmi_system_id __initdata samsung_dmi_table[] = {
551 	{
552 		.ident = "N128",
553 		.matches = {
554 			DMI_MATCH(DMI_SYS_VENDOR,
555 					"SAMSUNG ELECTRONICS CO., LTD."),
556 			DMI_MATCH(DMI_PRODUCT_NAME, "N128"),
557 			DMI_MATCH(DMI_BOARD_NAME, "N128"),
558 		},
559 		.callback = dmi_check_cb,
560 	},
561 	{
562 		.ident = "N130",
563 		.matches = {
564 			DMI_MATCH(DMI_SYS_VENDOR,
565 					"SAMSUNG ELECTRONICS CO., LTD."),
566 			DMI_MATCH(DMI_PRODUCT_NAME, "N130"),
567 			DMI_MATCH(DMI_BOARD_NAME, "N130"),
568 		},
569 		.callback = dmi_check_cb,
570 	},
571 	{
572 		.ident = "N510",
573 		.matches = {
574 			DMI_MATCH(DMI_SYS_VENDOR,
575 					"SAMSUNG ELECTRONICS CO., LTD."),
576 			DMI_MATCH(DMI_PRODUCT_NAME, "N510"),
577 			DMI_MATCH(DMI_BOARD_NAME, "N510"),
578 		},
579 		.callback = dmi_check_cb,
580 	},
581 	{
582 		.ident = "X125",
583 		.matches = {
584 			DMI_MATCH(DMI_SYS_VENDOR,
585 					"SAMSUNG ELECTRONICS CO., LTD."),
586 			DMI_MATCH(DMI_PRODUCT_NAME, "X125"),
587 			DMI_MATCH(DMI_BOARD_NAME, "X125"),
588 		},
589 		.callback = dmi_check_cb,
590 	},
591 	{
592 		.ident = "X120/X170",
593 		.matches = {
594 			DMI_MATCH(DMI_SYS_VENDOR,
595 					"SAMSUNG ELECTRONICS CO., LTD."),
596 			DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"),
597 			DMI_MATCH(DMI_BOARD_NAME, "X120/X170"),
598 		},
599 		.callback = dmi_check_cb,
600 	},
601 	{
602 		.ident = "NC10",
603 		.matches = {
604 			DMI_MATCH(DMI_SYS_VENDOR,
605 					"SAMSUNG ELECTRONICS CO., LTD."),
606 			DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
607 			DMI_MATCH(DMI_BOARD_NAME, "NC10"),
608 		},
609 		.callback = dmi_check_cb,
610 	},
611 		{
612 		.ident = "NP-Q45",
613 		.matches = {
614 			DMI_MATCH(DMI_SYS_VENDOR,
615 					"SAMSUNG ELECTRONICS CO., LTD."),
616 			DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
617 			DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"),
618 		},
619 		.callback = dmi_check_cb,
620 		},
621 	{
622 		.ident = "X360",
623 		.matches = {
624 			DMI_MATCH(DMI_SYS_VENDOR,
625 					"SAMSUNG ELECTRONICS CO., LTD."),
626 			DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
627 			DMI_MATCH(DMI_BOARD_NAME, "X360"),
628 		},
629 		.callback = dmi_check_cb,
630 	},
631 	{
632 		.ident = "R410 Plus",
633 		.matches = {
634 			DMI_MATCH(DMI_SYS_VENDOR,
635 					"SAMSUNG ELECTRONICS CO., LTD."),
636 			DMI_MATCH(DMI_PRODUCT_NAME, "R410P"),
637 			DMI_MATCH(DMI_BOARD_NAME, "R460"),
638 		},
639 		.callback = dmi_check_cb,
640 	},
641 	{
642 		.ident = "R518",
643 		.matches = {
644 			DMI_MATCH(DMI_SYS_VENDOR,
645 					"SAMSUNG ELECTRONICS CO., LTD."),
646 			DMI_MATCH(DMI_PRODUCT_NAME, "R518"),
647 			DMI_MATCH(DMI_BOARD_NAME, "R518"),
648 		},
649 		.callback = dmi_check_cb,
650 	},
651 	{
652 		.ident = "R519/R719",
653 		.matches = {
654 			DMI_MATCH(DMI_SYS_VENDOR,
655 					"SAMSUNG ELECTRONICS CO., LTD."),
656 			DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"),
657 			DMI_MATCH(DMI_BOARD_NAME, "R519/R719"),
658 		},
659 		.callback = dmi_check_cb,
660 	},
661 	{
662 		.ident = "N150/N210/N220",
663 		.matches = {
664 			DMI_MATCH(DMI_SYS_VENDOR,
665 					"SAMSUNG ELECTRONICS CO., LTD."),
666 			DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
667 			DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
668 		},
669 		.callback = dmi_check_cb,
670 	},
671 	{
672 		.ident = "N220",
673 		.matches = {
674 			DMI_MATCH(DMI_SYS_VENDOR,
675 					"SAMSUNG ELECTRONICS CO., LTD."),
676 			DMI_MATCH(DMI_PRODUCT_NAME, "N220"),
677 			DMI_MATCH(DMI_BOARD_NAME, "N220"),
678 		},
679 		.callback = dmi_check_cb,
680 	},
681 	{
682 		.ident = "N150/N210/N220/N230",
683 		.matches = {
684 			DMI_MATCH(DMI_SYS_VENDOR,
685 					"SAMSUNG ELECTRONICS CO., LTD."),
686 			DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220/N230"),
687 			DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220/N230"),
688 		},
689 		.callback = dmi_check_cb,
690 	},
691 	{
692 		.ident = "N150P/N210P/N220P",
693 		.matches = {
694 			DMI_MATCH(DMI_SYS_VENDOR,
695 					"SAMSUNG ELECTRONICS CO., LTD."),
696 			DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"),
697 			DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"),
698 		},
699 		.callback = dmi_check_cb,
700 	},
701 	{
702 		.ident = "R700",
703 		.matches = {
704 		      DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
705 		      DMI_MATCH(DMI_PRODUCT_NAME, "SR700"),
706 		      DMI_MATCH(DMI_BOARD_NAME, "SR700"),
707 		},
708 		.callback = dmi_check_cb,
709 	},
710 	{
711 		.ident = "R530/R730",
712 		.matches = {
713 		      DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
714 		      DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"),
715 		      DMI_MATCH(DMI_BOARD_NAME, "R530/R730"),
716 		},
717 		.callback = dmi_check_cb,
718 	},
719 	{
720 		.ident = "NF110/NF210/NF310",
721 		.matches = {
722 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
723 			DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
724 			DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
725 		},
726 		.callback = dmi_check_cb,
727 	},
728 	{
729 		.ident = "N145P/N250P/N260P",
730 		.matches = {
731 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
732 			DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
733 			DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
734 		},
735 		.callback = dmi_check_cb,
736 	},
737 	{
738 		.ident = "R70/R71",
739 		.matches = {
740 			DMI_MATCH(DMI_SYS_VENDOR,
741 					"SAMSUNG ELECTRONICS CO., LTD."),
742 			DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"),
743 			DMI_MATCH(DMI_BOARD_NAME, "R70/R71"),
744 		},
745 		.callback = dmi_check_cb,
746 	},
747 	{
748 		.ident = "P460",
749 		.matches = {
750 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
751 			DMI_MATCH(DMI_PRODUCT_NAME, "P460"),
752 			DMI_MATCH(DMI_BOARD_NAME, "P460"),
753 		},
754 		.callback = dmi_check_cb,
755 	},
756 	{
757 		.ident = "R528/R728",
758 		.matches = {
759 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
760 			DMI_MATCH(DMI_PRODUCT_NAME, "R528/R728"),
761 			DMI_MATCH(DMI_BOARD_NAME, "R528/R728"),
762 		},
763 		.callback = dmi_check_cb,
764 	},
765 	{
766 		.ident = "NC210/NC110",
767 		.matches = {
768 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
769 			DMI_MATCH(DMI_PRODUCT_NAME, "NC210/NC110"),
770 			DMI_MATCH(DMI_BOARD_NAME, "NC210/NC110"),
771 		},
772 		.callback = dmi_check_cb,
773 	},
774 		{
775 		.ident = "X520",
776 		.matches = {
777 			DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
778 			DMI_MATCH(DMI_PRODUCT_NAME, "X520"),
779 			DMI_MATCH(DMI_BOARD_NAME, "X520"),
780 		},
781 		.callback = dmi_check_cb,
782 	},
783 	{ },
784 };
785 MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
786 
787 static int find_signature(void __iomem *memcheck, const char *testStr)
788 {
789 	int i = 0;
790 	int loca;
791 
792 	for (loca = 0; loca < 0xffff; loca++) {
793 		char temp = readb(memcheck + loca);
794 
795 		if (temp == testStr[i]) {
796 			if (i == strlen(testStr)-1)
797 				break;
798 			++i;
799 		} else {
800 			i = 0;
801 		}
802 	}
803 	return loca;
804 }
805 
806 static int __init samsung_init(void)
807 {
808 	struct backlight_properties props;
809 	struct sabi_retval sretval;
810 	unsigned int ifaceP;
811 	int i;
812 	int loca;
813 	int retval;
814 
815 	mutex_init(&sabi_mutex);
816 
817 	if (!force && !dmi_check_system(samsung_dmi_table))
818 		return -ENODEV;
819 
820 	f0000_segment = ioremap_nocache(0xf0000, 0xffff);
821 	if (!f0000_segment) {
822 		pr_err("Can't map the segment at 0xf0000\n");
823 		return -EINVAL;
824 	}
825 
826 	/* Try to find one of the signatures in memory to find the header */
827 	for (i = 0; sabi_configs[i].test_string != 0; ++i) {
828 		sabi_config = &sabi_configs[i];
829 		loca = find_signature(f0000_segment, sabi_config->test_string);
830 		if (loca != 0xffff)
831 			break;
832 	}
833 
834 	if (loca == 0xffff) {
835 		pr_err("This computer does not support SABI\n");
836 		goto error_no_signature;
837 	}
838 
839 	/* point to the SMI port Number */
840 	loca += 1;
841 	sabi = (f0000_segment + loca);
842 
843 	if (debug) {
844 		printk(KERN_DEBUG "This computer supports SABI==%x\n",
845 			loca + 0xf0000 - 6);
846 		printk(KERN_DEBUG "SABI header:\n");
847 		printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
848 			readw(sabi + sabi_config->header_offsets.port));
849 		printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
850 			readb(sabi + sabi_config->header_offsets.iface_func));
851 		printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
852 			readb(sabi + sabi_config->header_offsets.en_mem));
853 		printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
854 			readb(sabi + sabi_config->header_offsets.re_mem));
855 		printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
856 			readw(sabi + sabi_config->header_offsets.data_offset));
857 		printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
858 			readw(sabi + sabi_config->header_offsets.data_segment));
859 	}
860 
861 	/* Get a pointer to the SABI Interface */
862 	ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4;
863 	ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff;
864 	sabi_iface = ioremap_nocache(ifaceP, 16);
865 	if (!sabi_iface) {
866 		pr_err("Can't remap %x\n", ifaceP);
867 		goto error_no_signature;
868 	}
869 	if (debug) {
870 		printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP);
871 		printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface);
872 
873 		test_backlight();
874 		test_wireless();
875 
876 		retval = sabi_get_command(sabi_config->commands.get_brightness,
877 					  &sretval);
878 		printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]);
879 	}
880 
881 	/* Turn on "Linux" mode in the BIOS */
882 	if (sabi_config->commands.set_linux != 0xff) {
883 		retval = sabi_set_command(sabi_config->commands.set_linux,
884 					  0x81);
885 		if (retval) {
886 			pr_warn("Linux mode was not set!\n");
887 			goto error_no_platform;
888 		}
889 	}
890 
891 	/* Check for stepping quirk */
892 	check_for_stepping_quirk();
893 
894 	/* knock up a platform device to hang stuff off of */
895 	sdev = platform_device_register_simple("samsung", -1, NULL, 0);
896 	if (IS_ERR(sdev))
897 		goto error_no_platform;
898 
899 	/* create a backlight device to talk to this one */
900 	memset(&props, 0, sizeof(struct backlight_properties));
901 	props.type = BACKLIGHT_PLATFORM;
902 	props.max_brightness = sabi_config->max_brightness -
903 				sabi_config->min_brightness;
904 	backlight_device = backlight_device_register("samsung", &sdev->dev,
905 						     NULL, &backlight_ops,
906 						     &props);
907 	if (IS_ERR(backlight_device))
908 		goto error_no_backlight;
909 
910 	backlight_device->props.brightness = read_brightness();
911 	backlight_device->props.power = FB_BLANK_UNBLANK;
912 	backlight_update_status(backlight_device);
913 
914 	retval = init_wireless(sdev);
915 	if (retval)
916 		goto error_no_rfk;
917 
918 	retval = device_create_file(&sdev->dev, &dev_attr_performance_level);
919 	if (retval)
920 		goto error_file_create;
921 
922 	return 0;
923 
924 error_file_create:
925 	destroy_wireless();
926 
927 error_no_rfk:
928 	backlight_device_unregister(backlight_device);
929 
930 error_no_backlight:
931 	platform_device_unregister(sdev);
932 
933 error_no_platform:
934 	iounmap(sabi_iface);
935 
936 error_no_signature:
937 	iounmap(f0000_segment);
938 	return -EINVAL;
939 }
940 
941 static void __exit samsung_exit(void)
942 {
943 	/* Turn off "Linux" mode in the BIOS */
944 	if (sabi_config->commands.set_linux != 0xff)
945 		sabi_set_command(sabi_config->commands.set_linux, 0x80);
946 
947 	device_remove_file(&sdev->dev, &dev_attr_performance_level);
948 	backlight_device_unregister(backlight_device);
949 	destroy_wireless();
950 	iounmap(sabi_iface);
951 	iounmap(f0000_segment);
952 	platform_device_unregister(sdev);
953 }
954 
955 module_init(samsung_init);
956 module_exit(samsung_exit);
957 
958 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
959 MODULE_DESCRIPTION("Samsung Backlight driver");
960 MODULE_LICENSE("GPL");
961