Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.8.
   1/*
   2 *  asus_acpi.c - Asus Laptop ACPI Extras
   3 *
   4 *
   5 *  Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
   6 *
   7 *  This program is free software; you can redistribute it and/or modify
   8 *  it under the terms of the GNU General Public License as published by
   9 *  the Free Software Foundation; either version 2 of the License, or
  10 *  (at your option) any later version.
  11 *
  12 *  This program is distributed in the hope that it will be useful,
  13 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 *  GNU General Public License for more details.
  16 *
  17 *  You should have received a copy of the GNU General Public License
  18 *  along with this program; if not, write to the Free Software
  19 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20 *
  21 *
  22 *  The development page for this driver is located at
  23 *  http://sourceforge.net/projects/acpi4asus/
  24 *
  25 *  Credits:
  26 *  Pontus Fuchs   - Helper functions, cleanup
  27 *  Johann Wiesner - Small compile fixes
  28 *  John Belmonte  - ACPI code for Toshiba laptop was a good starting point.
  29 *  �ic Burghard  - LED display support for W1N
  30 *
  31 */
  32
  33#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  34
  35#include <linux/kernel.h>
  36#include <linux/module.h>
  37#include <linux/slab.h>
  38#include <linux/init.h>
  39#include <linux/types.h>
  40#include <linux/proc_fs.h>
  41#include <linux/seq_file.h>
  42#include <linux/backlight.h>
  43#include <acpi/acpi_drivers.h>
  44#include <acpi/acpi_bus.h>
  45#include <asm/uaccess.h>
  46
  47#define ASUS_ACPI_VERSION "0.30"
  48
  49#define PROC_ASUS       "asus"	/* The directory */
  50#define PROC_MLED       "mled"
  51#define PROC_WLED       "wled"
  52#define PROC_TLED       "tled"
  53#define PROC_BT         "bluetooth"
  54#define PROC_LEDD       "ledd"
  55#define PROC_INFO       "info"
  56#define PROC_LCD        "lcd"
  57#define PROC_BRN        "brn"
  58#define PROC_DISP       "disp"
  59
  60#define ACPI_HOTK_NAME          "Asus Laptop ACPI Extras Driver"
  61#define ACPI_HOTK_CLASS         "hotkey"
  62#define ACPI_HOTK_DEVICE_NAME   "Hotkey"
  63
  64/*
  65 * Some events we use, same for all Asus
  66 */
  67#define BR_UP       0x10
  68#define BR_DOWN     0x20
  69
  70/*
  71 * Flags for hotk status
  72 */
  73#define MLED_ON     0x01	/* Mail LED */
  74#define WLED_ON     0x02	/* Wireless LED */
  75#define TLED_ON     0x04	/* Touchpad LED */
  76#define BT_ON       0x08	/* Internal Bluetooth */
  77
  78MODULE_AUTHOR("Julien Lerouge, Karol Kozimor");
  79MODULE_DESCRIPTION(ACPI_HOTK_NAME);
  80MODULE_LICENSE("GPL");
  81
  82static uid_t asus_uid;
  83static gid_t asus_gid;
  84module_param(asus_uid, uint, 0);
  85MODULE_PARM_DESC(asus_uid, "UID for entries in /proc/acpi/asus");
  86module_param(asus_gid, uint, 0);
  87MODULE_PARM_DESC(asus_gid, "GID for entries in /proc/acpi/asus");
  88
  89/* For each model, all features implemented,
  90 * those marked with R are relative to HOTK, A for absolute */
  91struct model_data {
  92	char *name;		/* name of the laptop________________A */
  93	char *mt_mled;		/* method to handle mled_____________R */
  94	char *mled_status;	/* node to handle mled reading_______A */
  95	char *mt_wled;		/* method to handle wled_____________R */
  96	char *wled_status;	/* node to handle wled reading_______A */
  97	char *mt_tled;		/* method to handle tled_____________R */
  98	char *tled_status;	/* node to handle tled reading_______A */
  99	char *mt_ledd;		/* method to handle LED display______R */
 100	char *mt_bt_switch;	/* method to switch Bluetooth on/off_R */
 101	char *bt_status;	/* no model currently supports this__? */
 102	char *mt_lcd_switch;	/* method to turn LCD on/off_________A */
 103	char *lcd_status;	/* node to read LCD panel state______A */
 104	char *brightness_up;	/* method to set brightness up_______A */
 105	char *brightness_down;	/* method to set brightness down ____A */
 106	char *brightness_set;	/* method to set absolute brightness_R */
 107	char *brightness_get;	/* method to get absolute brightness_R */
 108	char *brightness_status;/* node to get brightness____________A */
 109	char *display_set;	/* method to set video output________R */
 110	char *display_get;	/* method to get video output________R */
 111};
 112
 113/*
 114 * This is the main structure, we can use it to store anything interesting
 115 * about the hotk device
 116 */
 117struct asus_hotk {
 118	struct acpi_device *device;	/* the device we are in */
 119	acpi_handle handle;		/* the handle of the hotk device */
 120	char status;			/* status of the hotk, for LEDs */
 121	u32 ledd_status;		/* status of the LED display */
 122	struct model_data *methods;	/* methods available on the laptop */
 123	u8 brightness;			/* brightness level */
 124	enum {
 125		A1x = 0,	/* A1340D, A1300F */
 126		A2x,		/* A2500H */
 127		A4G,		/* A4700G */
 128		D1x,		/* D1 */
 129		L2D,		/* L2000D */
 130		L3C,		/* L3800C */
 131		L3D,		/* L3400D */
 132		L3H,		/* L3H, L2000E, L5D */
 133		L4R,		/* L4500R */
 134		L5x,		/* L5800C */
 135		L8L,		/* L8400L */
 136		M1A,		/* M1300A */
 137		M2E,		/* M2400E, L4400L */
 138		M6N,		/* M6800N, W3400N */
 139		M6R,		/* M6700R, A3000G */
 140		P30,		/* Samsung P30 */
 141		S1x,		/* S1300A, but also L1400B and M2400A (L84F) */
 142		S2x,		/* S200 (J1 reported), Victor MP-XP7210 */
 143		W1N,		/* W1000N */
 144		W5A,		/* W5A */
 145		W3V,            /* W3030V */
 146		xxN,		/* M2400N, M3700N, M5200N, M6800N,
 147							 S1300N, S5200N*/
 148		A4S,            /* Z81sp */
 149		F3Sa,		/* (Centrino) */
 150		R1F,
 151		END_MODEL
 152	} model;		/* Models currently supported */
 153	u16 event_count[128];	/* Count for each event TODO make this better */
 154};
 155
 156/* Here we go */
 157#define A1x_PREFIX "\\_SB.PCI0.ISA.EC0."
 158#define L3C_PREFIX "\\_SB.PCI0.PX40.ECD0."
 159#define M1A_PREFIX "\\_SB.PCI0.PX40.EC0."
 160#define P30_PREFIX "\\_SB.PCI0.LPCB.EC0."
 161#define S1x_PREFIX "\\_SB.PCI0.PX40."
 162#define S2x_PREFIX A1x_PREFIX
 163#define xxN_PREFIX "\\_SB.PCI0.SBRG.EC0."
 164
 165static struct model_data model_conf[END_MODEL] = {
 166	/*
 167	 * TODO I have seen a SWBX and AIBX method on some models, like L1400B,
 168	 * it seems to be a kind of switch, but what for ?
 169	 */
 170
 171	{
 172	 .name = "A1x",
 173	 .mt_mled = "MLED",
 174	 .mled_status = "\\MAIL",
 175	 .mt_lcd_switch = A1x_PREFIX "_Q10",
 176	 .lcd_status = "\\BKLI",
 177	 .brightness_up = A1x_PREFIX "_Q0E",
 178	 .brightness_down = A1x_PREFIX "_Q0F"},
 179
 180	{
 181	 .name = "A2x",
 182	 .mt_mled = "MLED",
 183	 .mt_wled = "WLED",
 184	 .wled_status = "\\SG66",
 185	 .mt_lcd_switch = "\\Q10",
 186	 .lcd_status = "\\BAOF",
 187	 .brightness_set = "SPLV",
 188	 .brightness_get = "GPLV",
 189	 .display_set = "SDSP",
 190	 .display_get = "\\INFB"},
 191
 192	{
 193	 .name = "A4G",
 194	 .mt_mled = "MLED",
 195/* WLED present, but not controlled by ACPI */
 196	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 197	 .brightness_set = "SPLV",
 198	 .brightness_get = "GPLV",
 199	 .display_set = "SDSP",
 200	 .display_get = "\\ADVG"},
 201
 202	{
 203	 .name = "D1x",
 204	 .mt_mled = "MLED",
 205	 .mt_lcd_switch = "\\Q0D",
 206	 .lcd_status = "\\GP11",
 207	 .brightness_up = "\\Q0C",
 208	 .brightness_down = "\\Q0B",
 209	 .brightness_status = "\\BLVL",
 210	 .display_set = "SDSP",
 211	 .display_get = "\\INFB"},
 212
 213	{
 214	 .name = "L2D",
 215	 .mt_mled = "MLED",
 216	 .mled_status = "\\SGP6",
 217	 .mt_wled = "WLED",
 218	 .wled_status = "\\RCP3",
 219	 .mt_lcd_switch = "\\Q10",
 220	 .lcd_status = "\\SGP0",
 221	 .brightness_up = "\\Q0E",
 222	 .brightness_down = "\\Q0F",
 223	 .display_set = "SDSP",
 224	 .display_get = "\\INFB"},
 225
 226	{
 227	 .name = "L3C",
 228	 .mt_mled = "MLED",
 229	 .mt_wled = "WLED",
 230	 .mt_lcd_switch = L3C_PREFIX "_Q10",
 231	 .lcd_status = "\\GL32",
 232	 .brightness_set = "SPLV",
 233	 .brightness_get = "GPLV",
 234	 .display_set = "SDSP",
 235	 .display_get = "\\_SB.PCI0.PCI1.VGAC.NMAP"},
 236
 237	{
 238	 .name = "L3D",
 239	 .mt_mled = "MLED",
 240	 .mled_status = "\\MALD",
 241	 .mt_wled = "WLED",
 242	 .mt_lcd_switch = "\\Q10",
 243	 .lcd_status = "\\BKLG",
 244	 .brightness_set = "SPLV",
 245	 .brightness_get = "GPLV",
 246	 .display_set = "SDSP",
 247	 .display_get = "\\INFB"},
 248
 249	{
 250	 .name = "L3H",
 251	 .mt_mled = "MLED",
 252	 .mt_wled = "WLED",
 253	 .mt_lcd_switch = "EHK",
 254	 .lcd_status = "\\_SB.PCI0.PM.PBC",
 255	 .brightness_set = "SPLV",
 256	 .brightness_get = "GPLV",
 257	 .display_set = "SDSP",
 258	 .display_get = "\\INFB"},
 259
 260	{
 261	 .name = "L4R",
 262	 .mt_mled = "MLED",
 263	 .mt_wled = "WLED",
 264	 .wled_status = "\\_SB.PCI0.SBRG.SG13",
 265	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 266	 .lcd_status = "\\_SB.PCI0.SBSM.SEO4",
 267	 .brightness_set = "SPLV",
 268	 .brightness_get = "GPLV",
 269	 .display_set = "SDSP",
 270	 .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"},
 271
 272	{
 273	 .name = "L5x",
 274	 .mt_mled = "MLED",
 275/* WLED present, but not controlled by ACPI */
 276	 .mt_tled = "TLED",
 277	 .mt_lcd_switch = "\\Q0D",
 278	 .lcd_status = "\\BAOF",
 279	 .brightness_set = "SPLV",
 280	 .brightness_get = "GPLV",
 281	 .display_set = "SDSP",
 282	 .display_get = "\\INFB"},
 283
 284	{
 285	 .name = "L8L"
 286/* No features, but at least support the hotkeys */
 287	 },
 288
 289	{
 290	 .name = "M1A",
 291	 .mt_mled = "MLED",
 292	 .mt_lcd_switch = M1A_PREFIX "Q10",
 293	 .lcd_status = "\\PNOF",
 294	 .brightness_up = M1A_PREFIX "Q0E",
 295	 .brightness_down = M1A_PREFIX "Q0F",
 296	 .brightness_status = "\\BRIT",
 297	 .display_set = "SDSP",
 298	 .display_get = "\\INFB"},
 299
 300	{
 301	 .name = "M2E",
 302	 .mt_mled = "MLED",
 303	 .mt_wled = "WLED",
 304	 .mt_lcd_switch = "\\Q10",
 305	 .lcd_status = "\\GP06",
 306	 .brightness_set = "SPLV",
 307	 .brightness_get = "GPLV",
 308	 .display_set = "SDSP",
 309	 .display_get = "\\INFB"},
 310
 311	{
 312	 .name = "M6N",
 313	 .mt_mled = "MLED",
 314	 .mt_wled = "WLED",
 315	 .wled_status = "\\_SB.PCI0.SBRG.SG13",
 316	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 317	 .lcd_status = "\\_SB.BKLT",
 318	 .brightness_set = "SPLV",
 319	 .brightness_get = "GPLV",
 320	 .display_set = "SDSP",
 321	 .display_get = "\\SSTE"},
 322
 323	{
 324	 .name = "M6R",
 325	 .mt_mled = "MLED",
 326	 .mt_wled = "WLED",
 327	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 328	 .lcd_status = "\\_SB.PCI0.SBSM.SEO4",
 329	 .brightness_set = "SPLV",
 330	 .brightness_get = "GPLV",
 331	 .display_set = "SDSP",
 332	 .display_get = "\\_SB.PCI0.P0P1.VGA.GETD"},
 333
 334	{
 335	 .name = "P30",
 336	 .mt_wled = "WLED",
 337	 .mt_lcd_switch = P30_PREFIX "_Q0E",
 338	 .lcd_status = "\\BKLT",
 339	 .brightness_up = P30_PREFIX "_Q68",
 340	 .brightness_down = P30_PREFIX "_Q69",
 341	 .brightness_get = "GPLV",
 342	 .display_set = "SDSP",
 343	 .display_get = "\\DNXT"},
 344
 345	{
 346	 .name = "S1x",
 347	 .mt_mled = "MLED",
 348	 .mled_status = "\\EMLE",
 349	 .mt_wled = "WLED",
 350	 .mt_lcd_switch = S1x_PREFIX "Q10",
 351	 .lcd_status = "\\PNOF",
 352	 .brightness_set = "SPLV",
 353	 .brightness_get = "GPLV"},
 354
 355	{
 356	 .name = "S2x",
 357	 .mt_mled = "MLED",
 358	 .mled_status = "\\MAIL",
 359	 .mt_lcd_switch = S2x_PREFIX "_Q10",
 360	 .lcd_status = "\\BKLI",
 361	 .brightness_up = S2x_PREFIX "_Q0B",
 362	 .brightness_down = S2x_PREFIX "_Q0A"},
 363
 364	{
 365	 .name = "W1N",
 366	 .mt_mled = "MLED",
 367	 .mt_wled = "WLED",
 368	 .mt_ledd = "SLCM",
 369	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 370	 .lcd_status = "\\BKLT",
 371	 .brightness_set = "SPLV",
 372	 .brightness_get = "GPLV",
 373	 .display_set = "SDSP",
 374	 .display_get = "\\ADVG"},
 375
 376	{
 377	 .name = "W5A",
 378	 .mt_bt_switch = "BLED",
 379	 .mt_wled = "WLED",
 380	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 381	 .brightness_set = "SPLV",
 382	 .brightness_get = "GPLV",
 383	 .display_set = "SDSP",
 384	 .display_get = "\\ADVG"},
 385
 386	{
 387	 .name = "W3V",
 388	 .mt_mled = "MLED",
 389	 .mt_wled = "WLED",
 390	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 391	 .lcd_status = "\\BKLT",
 392	 .brightness_set = "SPLV",
 393	 .brightness_get = "GPLV",
 394	 .display_set = "SDSP",
 395	 .display_get = "\\INFB"},
 396
 397       {
 398	 .name = "xxN",
 399	 .mt_mled = "MLED",
 400/* WLED present, but not controlled by ACPI */
 401	 .mt_lcd_switch = xxN_PREFIX "_Q10",
 402	 .lcd_status = "\\BKLT",
 403	 .brightness_set = "SPLV",
 404	 .brightness_get = "GPLV",
 405	 .display_set = "SDSP",
 406	.display_get = "\\ADVG"},
 407
 408	{
 409		.name              = "A4S",
 410		.brightness_set    = "SPLV",
 411		.brightness_get    = "GPLV",
 412		.mt_bt_switch      = "BLED",
 413		.mt_wled           = "WLED"
 414	},
 415
 416	{
 417		.name		= "F3Sa",
 418		.mt_bt_switch	= "BLED",
 419		.mt_wled	= "WLED",
 420		.mt_mled	= "MLED",
 421		.brightness_get	= "GPLV",
 422		.brightness_set	= "SPLV",
 423		.mt_lcd_switch	= "\\_SB.PCI0.SBRG.EC0._Q10",
 424		.lcd_status	= "\\_SB.PCI0.SBRG.EC0.RPIN",
 425		.display_get	= "\\ADVG",
 426		.display_set	= "SDSP",
 427	},
 428	{
 429		.name = "R1F",
 430		.mt_bt_switch = "BLED",
 431		.mt_mled = "MLED",
 432		.mt_wled = "WLED",
 433		.mt_lcd_switch = "\\Q10",
 434		.lcd_status = "\\GP06",
 435		.brightness_set = "SPLV",
 436		.brightness_get = "GPLV",
 437		.display_set = "SDSP",
 438		.display_get = "\\INFB"
 439	}
 440};
 441
 442/* procdir we use */
 443static struct proc_dir_entry *asus_proc_dir;
 444
 445static struct backlight_device *asus_backlight_device;
 446
 447/*
 448 * This header is made available to allow proper configuration given model,
 449 * revision number , ... this info cannot go in struct asus_hotk because it is
 450 * available before the hotk
 451 */
 452static struct acpi_table_header *asus_info;
 453
 454/* The actual device the driver binds to */
 455static struct asus_hotk *hotk;
 456
 457/*
 458 * The hotkey driver and autoloading declaration
 459 */
 460static int asus_hotk_add(struct acpi_device *device);
 461static int asus_hotk_remove(struct acpi_device *device, int type);
 462static void asus_hotk_notify(struct acpi_device *device, u32 event);
 463
 464static const struct acpi_device_id asus_device_ids[] = {
 465	{"ATK0100", 0},
 466	{"", 0},
 467};
 468MODULE_DEVICE_TABLE(acpi, asus_device_ids);
 469
 470static struct acpi_driver asus_hotk_driver = {
 471	.name = "asus_acpi",
 472	.class = ACPI_HOTK_CLASS,
 473	.owner = THIS_MODULE,
 474	.ids = asus_device_ids,
 475	.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
 476	.ops = {
 477		.add = asus_hotk_add,
 478		.remove = asus_hotk_remove,
 479		.notify = asus_hotk_notify,
 480		},
 481};
 482
 483/*
 484 * This function evaluates an ACPI method, given an int as parameter, the
 485 * method is searched within the scope of the handle, can be NULL. The output
 486 * of the method is written is output, which can also be NULL
 487 *
 488 * returns 1 if write is successful, 0 else.
 489 */
 490static int write_acpi_int(acpi_handle handle, const char *method, int val,
 491			  struct acpi_buffer *output)
 492{
 493	struct acpi_object_list params;	/* list of input parameters (int) */
 494	union acpi_object in_obj;	/* the only param we use */
 495	acpi_status status;
 496
 497	params.count = 1;
 498	params.pointer = &in_obj;
 499	in_obj.type = ACPI_TYPE_INTEGER;
 500	in_obj.integer.value = val;
 501
 502	status = acpi_evaluate_object(handle, (char *)method, &params, output);
 503	return (status == AE_OK);
 504}
 505
 506static int read_acpi_int(acpi_handle handle, const char *method, int *val)
 507{
 508	struct acpi_buffer output;
 509	union acpi_object out_obj;
 510	acpi_status status;
 511
 512	output.length = sizeof(out_obj);
 513	output.pointer = &out_obj;
 514
 515	status = acpi_evaluate_object(handle, (char *)method, NULL, &output);
 516	*val = out_obj.integer.value;
 517	return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER);
 518}
 519
 520static int asus_info_proc_show(struct seq_file *m, void *v)
 521{
 522	int temp;
 523
 524	seq_printf(m, ACPI_HOTK_NAME " " ASUS_ACPI_VERSION "\n");
 525	seq_printf(m, "Model reference    : %s\n", hotk->methods->name);
 526	/*
 527	 * The SFUN method probably allows the original driver to get the list
 528	 * of features supported by a given model. For now, 0x0100 or 0x0800
 529	 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
 530	 * The significance of others is yet to be found.
 531	 */
 532	if (read_acpi_int(hotk->handle, "SFUN", &temp))
 533		seq_printf(m, "SFUN value         : 0x%04x\n", temp);
 534	/*
 535	 * Another value for userspace: the ASYM method returns 0x02 for
 536	 * battery low and 0x04 for battery critical, its readings tend to be
 537	 * more accurate than those provided by _BST.
 538	 * Note: since not all the laptops provide this method, errors are
 539	 * silently ignored.
 540	 */
 541	if (read_acpi_int(hotk->handle, "ASYM", &temp))
 542		seq_printf(m, "ASYM value         : 0x%04x\n", temp);
 543	if (asus_info) {
 544		seq_printf(m, "DSDT length        : %d\n", asus_info->length);
 545		seq_printf(m, "DSDT checksum      : %d\n", asus_info->checksum);
 546		seq_printf(m, "DSDT revision      : %d\n", asus_info->revision);
 547		seq_printf(m, "OEM id             : %.*s\n", ACPI_OEM_ID_SIZE, asus_info->oem_id);
 548		seq_printf(m, "OEM table id       : %.*s\n", ACPI_OEM_TABLE_ID_SIZE, asus_info->oem_table_id);
 549		seq_printf(m, "OEM revision       : 0x%x\n", asus_info->oem_revision);
 550		seq_printf(m, "ASL comp vendor id : %.*s\n", ACPI_NAME_SIZE, asus_info->asl_compiler_id);
 551		seq_printf(m, "ASL comp revision  : 0x%x\n", asus_info->asl_compiler_revision);
 552	}
 553
 554	return 0;
 555}
 556
 557static int asus_info_proc_open(struct inode *inode, struct file *file)
 558{
 559	return single_open(file, asus_info_proc_show, NULL);
 560}
 561
 562static const struct file_operations asus_info_proc_fops = {
 563	.owner		= THIS_MODULE,
 564	.open		= asus_info_proc_open,
 565	.read		= seq_read,
 566	.llseek		= seq_lseek,
 567	.release	= single_release,
 568};
 569
 570/*
 571 * /proc handlers
 572 * We write our info in page, we begin at offset off and cannot write more
 573 * than count bytes. We set eof to 1 if we handle those 2 values. We return the
 574 * number of bytes written in page
 575 */
 576
 577/* Generic LED functions */
 578static int read_led(const char *ledname, int ledmask)
 579{
 580	if (ledname) {
 581		int led_status;
 582
 583		if (read_acpi_int(NULL, ledname, &led_status))
 584			return led_status;
 585		else
 586			pr_warn("Error reading LED status\n");
 587	}
 588	return (hotk->status & ledmask) ? 1 : 0;
 589}
 590
 591static int parse_arg(const char __user *buf, unsigned long count, int *val)
 592{
 593	char s[32];
 594	if (!count)
 595		return 0;
 596	if (count > 31)
 597		return -EINVAL;
 598	if (copy_from_user(s, buf, count))
 599		return -EFAULT;
 600	s[count] = 0;
 601	if (sscanf(s, "%i", val) != 1)
 602		return -EINVAL;
 603	return count;
 604}
 605
 606/* FIXME: kill extraneous args so it can be called independently */
 607static int
 608write_led(const char __user *buffer, unsigned long count,
 609	  char *ledname, int ledmask, int invert)
 610{
 611	int rv, value;
 612	int led_out = 0;
 613
 614	rv = parse_arg(buffer, count, &value);
 615	if (rv > 0)
 616		led_out = value ? 1 : 0;
 617
 618	hotk->status =
 619	    (led_out) ? (hotk->status | ledmask) : (hotk->status & ~ledmask);
 620
 621	if (invert)		/* invert target value */
 622		led_out = !led_out;
 623
 624	if (!write_acpi_int(hotk->handle, ledname, led_out, NULL))
 625		pr_warn("LED (%s) write failed\n", ledname);
 626
 627	return rv;
 628}
 629
 630/*
 631 * Proc handlers for MLED
 632 */
 633static int mled_proc_show(struct seq_file *m, void *v)
 634{
 635	seq_printf(m, "%d\n", read_led(hotk->methods->mled_status, MLED_ON));
 636	return 0;
 637}
 638
 639static int mled_proc_open(struct inode *inode, struct file *file)
 640{
 641	return single_open(file, mled_proc_show, NULL);
 642}
 643
 644static ssize_t mled_proc_write(struct file *file, const char __user *buffer,
 645		size_t count, loff_t *pos)
 646{
 647	return write_led(buffer, count, hotk->methods->mt_mled, MLED_ON, 1);
 648}
 649
 650static const struct file_operations mled_proc_fops = {
 651	.owner		= THIS_MODULE,
 652	.open		= mled_proc_open,
 653	.read		= seq_read,
 654	.llseek		= seq_lseek,
 655	.release	= single_release,
 656	.write		= mled_proc_write,
 657};
 658
 659/*
 660 * Proc handlers for LED display
 661 */
 662static int ledd_proc_show(struct seq_file *m, void *v)
 663{
 664	seq_printf(m, "0x%08x\n", hotk->ledd_status);
 665	return 0;
 666}
 667
 668static int ledd_proc_open(struct inode *inode, struct file *file)
 669{
 670	return single_open(file, ledd_proc_show, NULL);
 671}
 672
 673static ssize_t ledd_proc_write(struct file *file, const char __user *buffer,
 674		size_t count, loff_t *pos)
 675{
 676	int rv, value;
 677
 678	rv = parse_arg(buffer, count, &value);
 679	if (rv > 0) {
 680		if (!write_acpi_int
 681		    (hotk->handle, hotk->methods->mt_ledd, value, NULL))
 682			pr_warn("LED display write failed\n");
 683		else
 684			hotk->ledd_status = (u32) value;
 685	}
 686	return rv;
 687}
 688
 689static const struct file_operations ledd_proc_fops = {
 690	.owner		= THIS_MODULE,
 691	.open		= ledd_proc_open,
 692	.read		= seq_read,
 693	.llseek		= seq_lseek,
 694	.release	= single_release,
 695	.write		= ledd_proc_write,
 696};
 697
 698/*
 699 * Proc handlers for WLED
 700 */
 701static int wled_proc_show(struct seq_file *m, void *v)
 702{
 703	seq_printf(m, "%d\n", read_led(hotk->methods->wled_status, WLED_ON));
 704	return 0;
 705}
 706
 707static int wled_proc_open(struct inode *inode, struct file *file)
 708{
 709	return single_open(file, wled_proc_show, NULL);
 710}
 711
 712static ssize_t wled_proc_write(struct file *file, const char __user *buffer,
 713		size_t count, loff_t *pos)
 714{
 715	return write_led(buffer, count, hotk->methods->mt_wled, WLED_ON, 0);
 716}
 717
 718static const struct file_operations wled_proc_fops = {
 719	.owner		= THIS_MODULE,
 720	.open		= wled_proc_open,
 721	.read		= seq_read,
 722	.llseek		= seq_lseek,
 723	.release	= single_release,
 724	.write		= wled_proc_write,
 725};
 726
 727/*
 728 * Proc handlers for Bluetooth
 729 */
 730static int bluetooth_proc_show(struct seq_file *m, void *v)
 731{
 732	seq_printf(m, "%d\n", read_led(hotk->methods->bt_status, BT_ON));
 733	return 0;
 734}
 735
 736static int bluetooth_proc_open(struct inode *inode, struct file *file)
 737{
 738	return single_open(file, bluetooth_proc_show, NULL);
 739}
 740
 741static ssize_t bluetooth_proc_write(struct file *file,
 742		const char __user *buffer, size_t count, loff_t *pos)
 743{
 744	/* Note: mt_bt_switch controls both internal Bluetooth adapter's
 745	   presence and its LED */
 746	return write_led(buffer, count, hotk->methods->mt_bt_switch, BT_ON, 0);
 747}
 748
 749static const struct file_operations bluetooth_proc_fops = {
 750	.owner		= THIS_MODULE,
 751	.open		= bluetooth_proc_open,
 752	.read		= seq_read,
 753	.llseek		= seq_lseek,
 754	.release	= single_release,
 755	.write		= bluetooth_proc_write,
 756};
 757
 758/*
 759 * Proc handlers for TLED
 760 */
 761static int tled_proc_show(struct seq_file *m, void *v)
 762{
 763	seq_printf(m, "%d\n", read_led(hotk->methods->tled_status, TLED_ON));
 764	return 0;
 765}
 766
 767static int tled_proc_open(struct inode *inode, struct file *file)
 768{
 769	return single_open(file, tled_proc_show, NULL);
 770}
 771
 772static ssize_t tled_proc_write(struct file *file, const char __user *buffer,
 773		size_t count, loff_t *pos)
 774{
 775	return write_led(buffer, count, hotk->methods->mt_tled, TLED_ON, 0);
 776}
 777
 778static const struct file_operations tled_proc_fops = {
 779	.owner		= THIS_MODULE,
 780	.open		= tled_proc_open,
 781	.read		= seq_read,
 782	.llseek		= seq_lseek,
 783	.release	= single_release,
 784	.write		= tled_proc_write,
 785};
 786
 787static int get_lcd_state(void)
 788{
 789	int lcd = 0;
 790
 791	if (hotk->model == L3H) {
 792		/* L3H and the like have to be handled differently */
 793		acpi_status status = 0;
 794		struct acpi_object_list input;
 795		union acpi_object mt_params[2];
 796		struct acpi_buffer output;
 797		union acpi_object out_obj;
 798
 799		input.count = 2;
 800		input.pointer = mt_params;
 801		/* Note: the following values are partly guessed up, but
 802		   otherwise they seem to work */
 803		mt_params[0].type = ACPI_TYPE_INTEGER;
 804		mt_params[0].integer.value = 0x02;
 805		mt_params[1].type = ACPI_TYPE_INTEGER;
 806		mt_params[1].integer.value = 0x02;
 807
 808		output.length = sizeof(out_obj);
 809		output.pointer = &out_obj;
 810
 811		status =
 812		    acpi_evaluate_object(NULL, hotk->methods->lcd_status,
 813					 &input, &output);
 814		if (status != AE_OK)
 815			return -1;
 816		if (out_obj.type == ACPI_TYPE_INTEGER)
 817			/* That's what the AML code does */
 818			lcd = out_obj.integer.value >> 8;
 819	} else if (hotk->model == F3Sa) {
 820		unsigned long long tmp;
 821		union acpi_object param;
 822		struct acpi_object_list input;
 823		acpi_status status;
 824
 825		/* Read pin 11 */
 826		param.type = ACPI_TYPE_INTEGER;
 827		param.integer.value = 0x11;
 828		input.count = 1;
 829		input.pointer = &param;
 830
 831		status = acpi_evaluate_integer(NULL, hotk->methods->lcd_status,
 832						&input, &tmp);
 833		if (status != AE_OK)
 834			return -1;
 835
 836		lcd = tmp;
 837	} else {
 838		/* We don't have to check anything if we are here */
 839		if (!read_acpi_int(NULL, hotk->methods->lcd_status, &lcd))
 840			pr_warn("Error reading LCD status\n");
 841
 842		if (hotk->model == L2D)
 843			lcd = ~lcd;
 844	}
 845
 846	return (lcd & 1);
 847}
 848
 849static int set_lcd_state(int value)
 850{
 851	int lcd = 0;
 852	acpi_status status = 0;
 853
 854	lcd = value ? 1 : 0;
 855	if (lcd != get_lcd_state()) {
 856		/* switch */
 857		if (hotk->model != L3H) {
 858			status =
 859			    acpi_evaluate_object(NULL,
 860						 hotk->methods->mt_lcd_switch,
 861						 NULL, NULL);
 862		} else {
 863			/* L3H and the like must be handled differently */
 864			if (!write_acpi_int
 865			    (hotk->handle, hotk->methods->mt_lcd_switch, 0x07,
 866			     NULL))
 867				status = AE_ERROR;
 868			/* L3H's AML executes EHK (0x07) upon Fn+F7 keypress,
 869			   the exact behaviour is simulated here */
 870		}
 871		if (ACPI_FAILURE(status))
 872			pr_warn("Error switching LCD\n");
 873	}
 874	return 0;
 875
 876}
 877
 878static int lcd_proc_show(struct seq_file *m, void *v)
 879{
 880	seq_printf(m, "%d\n", get_lcd_state());
 881	return 0;
 882}
 883
 884static int lcd_proc_open(struct inode *inode, struct file *file)
 885{
 886	return single_open(file, lcd_proc_show, NULL);
 887}
 888
 889static ssize_t lcd_proc_write(struct file *file, const char __user *buffer,
 890	       size_t count, loff_t *pos)
 891{
 892	int rv, value;
 893
 894	rv = parse_arg(buffer, count, &value);
 895	if (rv > 0)
 896		set_lcd_state(value);
 897	return rv;
 898}
 899
 900static const struct file_operations lcd_proc_fops = {
 901	.owner		= THIS_MODULE,
 902	.open		= lcd_proc_open,
 903	.read		= seq_read,
 904	.llseek		= seq_lseek,
 905	.release	= single_release,
 906	.write		= lcd_proc_write,
 907};
 908
 909static int read_brightness(struct backlight_device *bd)
 910{
 911	int value;
 912
 913	if (hotk->methods->brightness_get) {	/* SPLV/GPLV laptop */
 914		if (!read_acpi_int(hotk->handle, hotk->methods->brightness_get,
 915				   &value))
 916			pr_warn("Error reading brightness\n");
 917	} else if (hotk->methods->brightness_status) {	/* For D1 for example */
 918		if (!read_acpi_int(NULL, hotk->methods->brightness_status,
 919				   &value))
 920			pr_warn("Error reading brightness\n");
 921	} else			/* No GPLV method */
 922		value = hotk->brightness;
 923	return value;
 924}
 925
 926/*
 927 * Change the brightness level
 928 */
 929static int set_brightness(int value)
 930{
 931	acpi_status status = 0;
 932	int ret = 0;
 933
 934	/* SPLV laptop */
 935	if (hotk->methods->brightness_set) {
 936		if (!write_acpi_int(hotk->handle, hotk->methods->brightness_set,
 937				    value, NULL)) {
 938			pr_warn("Error changing brightness\n");
 939			ret = -EIO;
 940		}
 941		goto out;
 942	}
 943
 944	/* No SPLV method if we are here, act as appropriate */
 945	value -= read_brightness(NULL);
 946	while (value != 0) {
 947		status = acpi_evaluate_object(NULL, (value > 0) ?
 948					      hotk->methods->brightness_up :
 949					      hotk->methods->brightness_down,
 950					      NULL, NULL);
 951		(value > 0) ? value-- : value++;
 952		if (ACPI_FAILURE(status)) {
 953			pr_warn("Error changing brightness\n");
 954			ret = -EIO;
 955		}
 956	}
 957out:
 958	return ret;
 959}
 960
 961static int set_brightness_status(struct backlight_device *bd)
 962{
 963	return set_brightness(bd->props.brightness);
 964}
 965
 966static int brn_proc_show(struct seq_file *m, void *v)
 967{
 968	seq_printf(m, "%d\n", read_brightness(NULL));
 969	return 0;
 970}
 971
 972static int brn_proc_open(struct inode *inode, struct file *file)
 973{
 974	return single_open(file, brn_proc_show, NULL);
 975}
 976
 977static ssize_t brn_proc_write(struct file *file, const char __user *buffer,
 978	       size_t count, loff_t *pos)
 979{
 980	int rv, value;
 981
 982	rv = parse_arg(buffer, count, &value);
 983	if (rv > 0) {
 984		value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
 985		/* 0 <= value <= 15 */
 986		set_brightness(value);
 987	}
 988	return rv;
 989}
 990
 991static const struct file_operations brn_proc_fops = {
 992	.owner		= THIS_MODULE,
 993	.open		= brn_proc_open,
 994	.read		= seq_read,
 995	.llseek		= seq_lseek,
 996	.release	= single_release,
 997	.write		= brn_proc_write,
 998};
 999
1000static void set_display(int value)
1001{
1002	/* no sanity check needed for now */
1003	if (!write_acpi_int(hotk->handle, hotk->methods->display_set,
1004			    value, NULL))
1005		pr_warn("Error setting display\n");
1006	return;
1007}
1008
1009/*
1010 * Now, *this* one could be more user-friendly, but so far, no-one has
1011 * complained. The significance of bits is the same as in proc_write_disp()
1012 */
1013static int disp_proc_show(struct seq_file *m, void *v)
1014{
1015	int value = 0;
1016
1017	if (!read_acpi_int(hotk->handle, hotk->methods->display_get, &value))
1018		pr_warn("Error reading display status\n");
1019	value &= 0x07;	/* needed for some models, shouldn't hurt others */
1020	seq_printf(m, "%d\n", value);
1021	return 0;
1022}
1023
1024static int disp_proc_open(struct inode *inode, struct file *file)
1025{
1026	return single_open(file, disp_proc_show, NULL);
1027}
1028
1029/*
1030 * Experimental support for display switching. As of now: 1 should activate
1031 * the LCD output, 2 should do for CRT, and 4 for TV-Out. Any combination
1032 * (bitwise) of these will suffice. I never actually tested 3 displays hooked
1033 * up simultaneously, so be warned. See the acpi4asus README for more info.
1034 */
1035static ssize_t disp_proc_write(struct file *file, const char __user *buffer,
1036		size_t count, loff_t *pos)
1037{
1038	int rv, value;
1039
1040	rv = parse_arg(buffer, count, &value);
1041	if (rv > 0)
1042		set_display(value);
1043	return rv;
1044}
1045
1046static const struct file_operations disp_proc_fops = {
1047	.owner		= THIS_MODULE,
1048	.open		= disp_proc_open,
1049	.read		= seq_read,
1050	.llseek		= seq_lseek,
1051	.release	= single_release,
1052	.write		= disp_proc_write,
1053};
1054
1055static int
1056asus_proc_add(char *name, const struct file_operations *proc_fops, mode_t mode,
1057		     struct acpi_device *device)
1058{
1059	struct proc_dir_entry *proc;
1060
1061	proc = proc_create_data(name, mode, acpi_device_dir(device),
1062				proc_fops, acpi_driver_data(device));
1063	if (!proc) {
1064		pr_warn("  Unable to create %s fs entry\n", name);
1065		return -1;
1066	}
1067	proc->uid = asus_uid;
1068	proc->gid = asus_gid;
1069	return 0;
1070}
1071
1072static int asus_hotk_add_fs(struct acpi_device *device)
1073{
1074	struct proc_dir_entry *proc;
1075	mode_t mode;
1076
1077	if ((asus_uid == 0) && (asus_gid == 0)) {
1078		mode = S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP;
1079	} else {
1080		mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP;
1081		pr_warn("  asus_uid and asus_gid parameters are "
1082			"deprecated, use chown and chmod instead!\n");
1083	}
1084
1085	acpi_device_dir(device) = asus_proc_dir;
1086	if (!acpi_device_dir(device))
1087		return -ENODEV;
1088
1089	proc = proc_create(PROC_INFO, mode, acpi_device_dir(device),
1090			   &asus_info_proc_fops);
1091	if (proc) {
1092		proc->uid = asus_uid;
1093		proc->gid = asus_gid;
1094	} else {
1095		pr_warn("  Unable to create " PROC_INFO " fs entry\n");
1096	}
1097
1098	if (hotk->methods->mt_wled) {
1099		asus_proc_add(PROC_WLED, &wled_proc_fops, mode, device);
1100	}
1101
1102	if (hotk->methods->mt_ledd) {
1103		asus_proc_add(PROC_LEDD, &ledd_proc_fops, mode, device);
1104	}
1105
1106	if (hotk->methods->mt_mled) {
1107		asus_proc_add(PROC_MLED, &mled_proc_fops, mode, device);
1108	}
1109
1110	if (hotk->methods->mt_tled) {
1111		asus_proc_add(PROC_TLED, &tled_proc_fops, mode, device);
1112	}
1113
1114	if (hotk->methods->mt_bt_switch) {
1115		asus_proc_add(PROC_BT, &bluetooth_proc_fops, mode, device);
1116	}
1117
1118	/*
1119	 * We need both read node and write method as LCD switch is also
1120	 * accessible from the keyboard
1121	 */
1122	if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status) {
1123		asus_proc_add(PROC_LCD, &lcd_proc_fops, mode, device);
1124	}
1125
1126	if ((hotk->methods->brightness_up && hotk->methods->brightness_down) ||
1127	    (hotk->methods->brightness_get && hotk->methods->brightness_set)) {
1128		asus_proc_add(PROC_BRN, &brn_proc_fops, mode, device);
1129	}
1130
1131	if (hotk->methods->display_set) {
1132		asus_proc_add(PROC_DISP, &disp_proc_fops, mode, device);
1133	}
1134
1135	return 0;
1136}
1137
1138static int asus_hotk_remove_fs(struct acpi_device *device)
1139{
1140	if (acpi_device_dir(device)) {
1141		remove_proc_entry(PROC_INFO, acpi_device_dir(device));
1142		if (hotk->methods->mt_wled)
1143			remove_proc_entry(PROC_WLED, acpi_device_dir(device));
1144		if (hotk->methods->mt_mled)
1145			remove_proc_entry(PROC_MLED, acpi_device_dir(device));
1146		if (hotk->methods->mt_tled)
1147			remove_proc_entry(PROC_TLED, acpi_device_dir(device));
1148		if (hotk->methods->mt_ledd)
1149			remove_proc_entry(PROC_LEDD, acpi_device_dir(device));
1150		if (hotk->methods->mt_bt_switch)
1151			remove_proc_entry(PROC_BT, acpi_device_dir(device));
1152		if (hotk->methods->mt_lcd_switch && hotk->methods->lcd_status)
1153			remove_proc_entry(PROC_LCD, acpi_device_dir(device));
1154		if ((hotk->methods->brightness_up
1155		     && hotk->methods->brightness_down)
1156		    || (hotk->methods->brightness_get
1157			&& hotk->methods->brightness_set))
1158			remove_proc_entry(PROC_BRN, acpi_device_dir(device));
1159		if (hotk->methods->display_set)
1160			remove_proc_entry(PROC_DISP, acpi_device_dir(device));
1161	}
1162	return 0;
1163}
1164
1165static void asus_hotk_notify(struct acpi_device *device, u32 event)
1166{
1167	/* TODO Find a better way to handle events count. */
1168	if (!hotk)
1169		return;
1170
1171	/*
1172	 * The BIOS *should* be sending us device events, but apparently
1173	 * Asus uses system events instead, so just ignore any device
1174	 * events we get.
1175	 */
1176	if (event > ACPI_MAX_SYS_NOTIFY)
1177		return;
1178
1179	if ((event & ~((u32) BR_UP)) < 16)
1180		hotk->brightness = (event & ~((u32) BR_UP));
1181	else if ((event & ~((u32) BR_DOWN)) < 16)
1182		hotk->brightness = (event & ~((u32) BR_DOWN));
1183
1184	acpi_bus_generate_proc_event(hotk->device, event,
1185				hotk->event_count[event % 128]++);
1186
1187	return;
1188}
1189
1190/*
1191 * Match the model string to the list of supported models. Return END_MODEL if
1192 * no match or model is NULL.
1193 */
1194static int asus_model_match(char *model)
1195{
1196	if (model == NULL)
1197		return END_MODEL;
1198
1199	if (strncmp(model, "L3D", 3) == 0)
1200		return L3D;
1201	else if (strncmp(model, "L2E", 3) == 0 ||
1202		 strncmp(model, "L3H", 3) == 0 || strncmp(model, "L5D", 3) == 0)
1203		return L3H;
1204	else if (strncmp(model, "L3", 2) == 0 || strncmp(model, "L2B", 3) == 0)
1205		return L3C;
1206	else if (strncmp(model, "L8L", 3) == 0)
1207		return L8L;
1208	else if (strncmp(model, "L4R", 3) == 0)
1209		return L4R;
1210	else if (strncmp(model, "M6N", 3) == 0 || strncmp(model, "W3N", 3) == 0)
1211		return M6N;
1212	else if (strncmp(model, "M6R", 3) == 0 || strncmp(model, "A3G", 3) == 0)
1213		return M6R;
1214	else if (strncmp(model, "M2N", 3) == 0 ||
1215		 strncmp(model, "M3N", 3) == 0 ||
1216		 strncmp(model, "M5N", 3) == 0 ||
1217		 strncmp(model, "S1N", 3) == 0 ||
1218		 strncmp(model, "S5N", 3) == 0)
1219		return xxN;
1220	else if (strncmp(model, "M1", 2) == 0)
1221		return M1A;
1222	else if (strncmp(model, "M2", 2) == 0 || strncmp(model, "L4E", 3) == 0)
1223		return M2E;
1224	else if (strncmp(model, "L2", 2) == 0)
1225		return L2D;
1226	else if (strncmp(model, "L8", 2) == 0)
1227		return S1x;
1228	else if (strncmp(model, "D1", 2) == 0)
1229		return D1x;
1230	else if (strncmp(model, "A1", 2) == 0)
1231		return A1x;
1232	else if (strncmp(model, "A2", 2) == 0)
1233		return A2x;
1234	else if (strncmp(model, "J1", 2) == 0)
1235		return S2x;
1236	else if (strncmp(model, "L5", 2) == 0)
1237		return L5x;
1238	else if (strncmp(model, "A4G", 3) == 0)
1239		return A4G;
1240	else if (strncmp(model, "W1N", 3) == 0)
1241		return W1N;
1242	else if (strncmp(model, "W3V", 3) == 0)
1243		return W3V;
1244	else if (strncmp(model, "W5A", 3) == 0)
1245		return W5A;
1246	else if (strncmp(model, "R1F", 3) == 0)
1247		return R1F;
1248	else if (strncmp(model, "A4S", 3) == 0)
1249		return A4S;
1250	else if (strncmp(model, "F3Sa", 4) == 0)
1251		return F3Sa;
1252	else
1253		return END_MODEL;
1254}
1255
1256/*
1257 * This function is used to initialize the hotk with right values. In this
1258 * method, we can make all the detection we want, and modify the hotk struct
1259 */
1260static int asus_hotk_get_info(void)
1261{
1262	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
1263	union acpi_object *model = NULL;
1264	int bsts_result;
1265	char *string = NULL;
1266	acpi_status status;
1267
1268	/*
1269	 * Get DSDT headers early enough to allow for differentiating between
1270	 * models, but late enough to allow acpi_bus_register_driver() to fail
1271	 * before doing anything ACPI-specific. Should we encounter a machine,
1272	 * which needs special handling (i.e. its hotkey device has a different
1273	 * HID), this bit will be moved. A global variable asus_info contains
1274	 * the DSDT header.
1275	 */
1276	status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus_info);
1277	if (ACPI_FAILURE(status))
1278		pr_warn("  Couldn't get the DSDT table header\n");
1279
1280	/* We have to write 0 on init this far for all ASUS models */
1281	if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) {
1282		pr_err("  Hotkey initialization failed\n");
1283		return -ENODEV;
1284	}
1285
1286	/* This needs to be called for some laptops to init properly */
1287	if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result))
1288		pr_warn("  Error calling BSTS\n");
1289	else if (bsts_result)
1290		pr_notice("  BSTS called, 0x%02x returned\n", bsts_result);
1291
1292	/*
1293	 * Try to match the object returned by INIT to the specific model.
1294	 * Handle every possible object (or the lack of thereof) the DSDT
1295	 * writers might throw at us. When in trouble, we pass NULL to
1296	 * asus_model_match() and try something completely different.
1297	 */
1298	if (buffer.pointer) {
1299		model = buffer.pointer;
1300		switch (model->type) {
1301		case ACPI_TYPE_STRING:
1302			string = model->string.pointer;
1303			break;
1304		case ACPI_TYPE_BUFFER:
1305			string = model->buffer.pointer;
1306			break;
1307		default:
1308			kfree(model);
1309			model = NULL;
1310			break;
1311		}
1312	}
1313	hotk->model = asus_model_match(string);
1314	if (hotk->model == END_MODEL) {	/* match failed */
1315		if (asus_info &&
1316		    strncmp(asus_info->oem_table_id, "ODEM", 4) == 0) {
1317			hotk->model = P30;
1318			pr_notice("  Samsung P30 detected, supported\n");
1319			hotk->methods = &model_conf[hotk->model];
1320			kfree(model);
1321			return 0;
1322		} else {
1323			hotk->model = M2E;
1324			pr_notice("  unsupported model %s, trying default values\n",
1325				  string);
1326			pr_notice("  send /proc/acpi/dsdt to the developers\n");
1327			kfree(model);
1328			return -ENODEV;
1329		}
1330	}
1331	hotk->methods = &model_conf[hotk->model];
1332	pr_notice("  %s model detected, supported\n", string);
1333
1334	/* Sort of per-model blacklist */
1335	if (strncmp(string, "L2B", 3) == 0)
1336		hotk->methods->lcd_status = NULL;
1337	/* L2B is similar enough to L3C to use its settings, with this only
1338	   exception */
1339	else if (strncmp(string, "A3G", 3) == 0)
1340		hotk->methods->lcd_status = "\\BLFG";
1341	/* A3G is like M6R */
1342	else if (strncmp(string, "S5N", 3) == 0 ||
1343		 strncmp(string, "M5N", 3) == 0 ||
1344		 strncmp(string, "W3N", 3) == 0)
1345		hotk->methods->mt_mled = NULL;
1346	/* S5N, M5N and W3N have no MLED */
1347	else if (strncmp(string, "L5D", 3) == 0)
1348		hotk->methods->mt_wled = NULL;
1349	/* L5D's WLED is not controlled by ACPI */
1350	else if (strncmp(string, "M2N", 3) == 0 ||
1351		 strncmp(string, "W3V", 3) == 0 ||
1352		 strncmp(string, "S1N", 3) == 0)
1353		hotk->methods->mt_wled = "WLED";
1354	/* M2N, S1N and W3V have a usable WLED */
1355	else if (asus_info) {
1356		if (strncmp(asus_info->oem_table_id, "L1", 2) == 0)
1357			hotk->methods->mled_status = NULL;
1358		/* S1300A reports L84F, but L1400B too, account for that */
1359	}
1360
1361	kfree(model);
1362
1363	return 0;
1364}
1365
1366static int asus_hotk_check(void)
1367{
1368	int result = 0;
1369
1370	result = acpi_bus_get_status(hotk->device);
1371	if (result)
1372		return result;
1373
1374	if (hotk->device->status.present) {
1375		result = asus_hotk_get_info();
1376	} else {
1377		pr_err("  Hotkey device not present, aborting\n");
1378		return -EINVAL;
1379	}
1380
1381	return result;
1382}
1383
1384static int asus_hotk_found;
1385
1386static int asus_hotk_add(struct acpi_device *device)
1387{
1388	acpi_status status = AE_OK;
1389	int result;
1390
1391	pr_notice("Asus Laptop ACPI Extras version %s\n", ASUS_ACPI_VERSION);
1392
1393	hotk = kzalloc(sizeof(struct asus_hotk), GFP_KERNEL);
1394	if (!hotk)
1395		return -ENOMEM;
1396
1397	hotk->handle = device->handle;
1398	strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME);
1399	strcpy(acpi_device_class(device), ACPI_HOTK_CLASS);
1400	device->driver_data = hotk;
1401	hotk->device = device;
1402
1403	result = asus_hotk_check();
1404	if (result)
1405		goto end;
1406
1407	result = asus_hotk_add_fs(device);
1408	if (result)
1409		goto end;
1410
1411	/* For laptops without GPLV: init the hotk->brightness value */
1412	if ((!hotk->methods->brightness_get)
1413	    && (!hotk->methods->brightness_status)
1414	    && (hotk->methods->brightness_up && hotk->methods->brightness_down)) {
1415		status =
1416		    acpi_evaluate_object(NULL, hotk->methods->brightness_down,
1417					 NULL, NULL);
1418		if (ACPI_FAILURE(status))
1419			pr_warn("  Error changing brightness\n");
1420		else {
1421			status =
1422			    acpi_evaluate_object(NULL,
1423						 hotk->methods->brightness_up,
1424						 NULL, NULL);
1425			if (ACPI_FAILURE(status))
1426				pr_warn("  Strange, error changing brightness\n");
1427		}
1428	}
1429
1430	asus_hotk_found = 1;
1431
1432	/* LED display is off by default */
1433	hotk->ledd_status = 0xFFF;
1434
1435end:
1436	if (result)
1437		kfree(hotk);
1438
1439	return result;
1440}
1441
1442static int asus_hotk_remove(struct acpi_device *device, int type)
1443{
1444	asus_hotk_remove_fs(device);
1445
1446	kfree(hotk);
1447
1448	return 0;
1449}
1450
1451static const struct backlight_ops asus_backlight_data = {
1452	.get_brightness = read_brightness,
1453	.update_status  = set_brightness_status,
1454};
1455
1456static void asus_acpi_exit(void)
1457{
1458	if (asus_backlight_device)
1459		backlight_device_unregister(asus_backlight_device);
1460
1461	acpi_bus_unregister_driver(&asus_hotk_driver);
1462	remove_proc_entry(PROC_ASUS, acpi_root_dir);
1463
1464	return;
1465}
1466
1467static int __init asus_acpi_init(void)
1468{
1469	struct backlight_properties props;
1470	int result;
1471
1472	result = acpi_bus_register_driver(&asus_hotk_driver);
1473	if (result < 0)
1474		return result;
1475
1476	asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir);
1477	if (!asus_proc_dir) {
1478		pr_err("Unable to create /proc entry\n");
1479		acpi_bus_unregister_driver(&asus_hotk_driver);
1480		return -ENODEV;
1481	}
1482
1483	/*
1484	 * This is a bit of a kludge.  We only want this module loaded
1485	 * for ASUS systems, but there's currently no way to probe the
1486	 * ACPI namespace for ASUS HIDs.  So we just return failure if
1487	 * we didn't find one, which will cause the module to be
1488	 * unloaded.
1489	 */
1490	if (!asus_hotk_found) {
1491		acpi_bus_unregister_driver(&asus_hotk_driver);
1492		remove_proc_entry(PROC_ASUS, acpi_root_dir);
1493		return -ENODEV;
1494	}
1495
1496	memset(&props, 0, sizeof(struct backlight_properties));
1497	props.type = BACKLIGHT_PLATFORM;
1498	props.max_brightness = 15;
1499	asus_backlight_device = backlight_device_register("asus", NULL, NULL,
1500							  &asus_backlight_data,
1501							  &props);
1502	if (IS_ERR(asus_backlight_device)) {
1503		pr_err("Could not register asus backlight device\n");
1504		asus_backlight_device = NULL;
1505		asus_acpi_exit();
1506		return -ENODEV;
1507	}
1508
1509	return 0;
1510}
1511
1512module_init(asus_acpi_init);
1513module_exit(asus_acpi_exit);