File Index
Files and directories
Sign in
FILE

samsung-laptop.c

File preview with syntax highlighting.

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