aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCorentin Chary <corentincj@iksaif.net>2007-01-26 08:04:30 -0500
committerLen Brown <len.brown@intel.com>2007-01-30 01:36:57 -0500
commit85091b718969be7b8e6f795af7e264b8afcd7a6d (patch)
treed75b4598e47dee15b2ee22c26107f68017cb431e
parent5263bf65d6342e12ab716db8e529501670979321 (diff)
asus-laptop: add base driver
Adds the new driver and make ASUS_LAPTOP and ACPI_ASUS incompatible. It may be strange to use ASUS_CREATE_DEVICE_ATTR and ASUS_SET_DEVICE_ATTR now, but these macro will be very usefull in next patchs. ASUS_HANDLE and ASUS_HANDLE_INIT comes from IBM_HANDLE and IBM_HANDLE_INIT, with some modification, and will also be used in next patchs. Signed-off-by: Corentin Chary <corentincj@iksaif.net> Signed-off-by: Len Brown <len.brown@intel.com>
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/acpi/Kconfig13
-rw-r--r--drivers/misc/Kconfig17
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/asus-laptop.c530
5 files changed, 564 insertions, 5 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index f0596e452c5c..515289a3a437 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -584,6 +584,14 @@ W: http://sourceforge.net/projects/acpi4asus
584W: http://xf.iksaif.net/acpi4asus 584W: http://xf.iksaif.net/acpi4asus
585S: Maintained 585S: Maintained
586 586
587ASUS LAPTOP EXTRAS DRIVER
588P: Corentin Chary
589M: corentincj@iksaif.net
590L: acpi4asus-user@lists.sourceforge.net
591W: http://sourceforge.net/projects/acpi4asus
592W: http://xf.iksaif.net/acpi4asus
593S: Maintained
594
587ATA OVER ETHERNET DRIVER 595ATA OVER ETHERNET DRIVER
588P: Ed L. Cashin 596P: Ed L. Cashin
589M: ecashin@coraid.com 597M: ecashin@coraid.com
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index f4f000abc4e9..deed0de79a98 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -186,19 +186,22 @@ config ACPI_ASUS
186 186
187 Note: display switching code is currently considered EXPERIMENTAL, 187 Note: display switching code is currently considered EXPERIMENTAL,
188 toying with these values may even lock your machine. 188 toying with these values may even lock your machine.
189 189
190 All settings are changed via /proc/acpi/asus directory entries. Owner 190 All settings are changed via /proc/acpi/asus directory entries. Owner
191 and group for these entries can be set with asus_uid and asus_gid 191 and group for these entries can be set with asus_uid and asus_gid
192 parameters. 192 parameters.
193 193
194 More information and a userspace daemon for handling the extra buttons 194 More information and a userspace daemon for handling the extra buttons
195 at <http://sourceforge.net/projects/acpi4asus/>. 195 at <http://sourceforge.net/projects/acpi4asus/>.
196 196
197 If you have an ACPI-compatible ASUS laptop, say Y or M here. This 197 If you have an ACPI-compatible ASUS laptop, say Y or M here. This
198 driver is still under development, so if your laptop is unsupported or 198 driver is still under development, so if your laptop is unsupported or
199 something works not quite as expected, please use the mailing list 199 something works not quite as expected, please use the mailing list
200 available on the above page (acpi4asus-user@lists.sourceforge.net) 200 available on the above page (acpi4asus-user@lists.sourceforge.net).
201 201
202 NOTE: This driver is deprecated and will probably be removed soon,
203 use asus-laptop instead.
204
202config ACPI_IBM 205config ACPI_IBM
203 tristate "IBM ThinkPad Laptop Extras" 206 tristate "IBM ThinkPad Laptop Extras"
204 depends on X86 207 depends on X86
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 00db31c314e0..4b1e367e8feb 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -69,6 +69,23 @@ config TIFM_7XX1
69 To compile this driver as a module, choose M here: the module will 69 To compile this driver as a module, choose M here: the module will
70 be called tifm_7xx1. 70 be called tifm_7xx1.
71 71
72config ASUS_LAPTOP
73 tristate "Asus Laptop Extras (EXPERIMENTAL)"
74 depends on X86
75 depends on ACPI
76 depends on EXPERIMENTAL && !ACPI_ASUS
77 ---help---
78 This is the new Linux driver for Asus laptops. It may also support some
79 MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate
80 standard ACPI events that go through /proc/acpi/events. It also adds
81 support for video output switching, LCD backlight control, Bluetooth and
82 Wlan control, and most importantly, allows you to blink those fancy LEDs.
83
84 For more information and a userspace daemon for handling the extra
85 buttons see <http://acpi4asus.sf.net/>.
86
87 If you have an ACPI-compatible ASUS laptop, say Y or M here.
88
72config MSI_LAPTOP 89config MSI_LAPTOP
73 tristate "MSI Laptop Extras" 90 tristate "MSI Laptop Extras"
74 depends on X86 91 depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c9e98ab021c5..35da53c409c0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ obj- := misc.o # Dummy rule to force built-in.o to be made
6obj-$(CONFIG_IBM_ASM) += ibmasm/ 6obj-$(CONFIG_IBM_ASM) += ibmasm/
7obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/ 7obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
8obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o 8obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
9obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
9obj-$(CONFIG_LKDTM) += lkdtm.o 10obj-$(CONFIG_LKDTM) += lkdtm.o
10obj-$(CONFIG_TIFM_CORE) += tifm_core.o 11obj-$(CONFIG_TIFM_CORE) += tifm_core.o
11obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o 12obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o
diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c
new file mode 100644
index 000000000000..959b20f5604a
--- /dev/null
+++ b/drivers/misc/asus-laptop.c
@@ -0,0 +1,530 @@
1/*
2 * asus-laptop.c - Asus Laptop Support
3 *
4 *
5 * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
6 * Copyright (C) 2006 Corentin Chary
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 *
23 * The development page for this driver is located at
24 * http://sourceforge.net/projects/acpi4asus/
25 *
26 * Credits:
27 * Pontus Fuchs - Helper functions, cleanup
28 * Johann Wiesner - Small compile fixes
29 * John Belmonte - ACPI code for Toshiba laptop was a good starting point.
30 * Eric Burghard - LED display support for W1N
31 * Josh Green - Light Sens support
32 * Thomas Tuttle - His first patch for led support was very helpfull
33 *
34 */
35
36#include <linux/autoconf.h>
37#include <linux/kernel.h>
38#include <linux/module.h>
39#include <linux/init.h>
40#include <linux/types.h>
41#include <linux/err.h>
42#include <linux/proc_fs.h>
43#include <linux/platform_device.h>
44#include <acpi/acpi_drivers.h>
45#include <acpi/acpi_bus.h>
46#include <asm/uaccess.h>
47
48#define ASUS_LAPTOP_VERSION "0.40"
49
50#define ASUS_HOTK_NAME "Asus Laptop Support"
51#define ASUS_HOTK_CLASS "hotkey"
52#define ASUS_HOTK_DEVICE_NAME "Hotkey"
53#define ASUS_HOTK_HID "ATK0100"
54#define ASUS_HOTK_FILE "asus-laptop"
55#define ASUS_HOTK_PREFIX "\\_SB.ATKD."
56
57#define ASUS_LOG ASUS_HOTK_FILE ": "
58#define ASUS_ERR KERN_ERR ASUS_LOG
59#define ASUS_WARNING KERN_WARNING ASUS_LOG
60#define ASUS_NOTICE KERN_NOTICE ASUS_LOG
61#define ASUS_INFO KERN_INFO ASUS_LOG
62#define ASUS_DEBUG KERN_DEBUG ASUS_LOG
63
64MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
65MODULE_DESCRIPTION(ASUS_HOTK_NAME);
66MODULE_LICENSE("GPL");
67
68#define ASUS_HANDLE(object, paths...) \
69 static acpi_handle object##_handle = NULL; \
70 static char *object##_paths[] = { paths }
71
72/*
73 * This is the main structure, we can use it to store anything interesting
74 * about the hotk device
75 */
76struct asus_hotk {
77 char *name; //laptop name
78 struct acpi_device *device; //the device we are in
79 acpi_handle handle; //the handle of the hotk device
80 char status; //status of the hotk, for LEDs, ...
81 u16 event_count[128]; //count for each event TODO make this better
82};
83
84/*
85 * This header is made available to allow proper configuration given model,
86 * revision number , ... this info cannot go in struct asus_hotk because it is
87 * available before the hotk
88 */
89static struct acpi_table_header *asus_info;
90
91/* The actual device the driver binds to */
92static struct asus_hotk *hotk;
93
94/*
95 * The hotkey driver declaration
96 */
97static int asus_hotk_add(struct acpi_device *device);
98static int asus_hotk_remove(struct acpi_device *device, int type);
99static struct acpi_driver asus_hotk_driver = {
100 .name = ASUS_HOTK_NAME,
101 .class = ASUS_HOTK_CLASS,
102 .ids = ASUS_HOTK_HID,
103 .ops = {
104 .add = asus_hotk_add,
105 .remove = asus_hotk_remove,
106 },
107};
108
109/*
110 * This function evaluates an ACPI method, given an int as parameter, the
111 * method is searched within the scope of the handle, can be NULL. The output
112 * of the method is written is output, which can also be NULL
113 *
114 * returns 1 if write is successful, 0 else.
115 */
116static int write_acpi_int(acpi_handle handle, const char *method, int val,
117 struct acpi_buffer *output)
118{
119 struct acpi_object_list params; //list of input parameters (an int here)
120 union acpi_object in_obj; //the only param we use
121 acpi_status status;
122
123 params.count = 1;
124 params.pointer = &in_obj;
125 in_obj.type = ACPI_TYPE_INTEGER;
126 in_obj.integer.value = val;
127
128 status = acpi_evaluate_object(handle, (char *)method, &params, output);
129 return (status == AE_OK);
130}
131
132static int read_acpi_int(acpi_handle handle, const char *method, int *val,
133 struct acpi_object_list *params)
134{
135 struct acpi_buffer output;
136 union acpi_object out_obj;
137 acpi_status status;
138
139 output.length = sizeof(out_obj);
140 output.pointer = &out_obj;
141
142 status = acpi_evaluate_object(handle, (char *)method, params, &output);
143 *val = out_obj.integer.value;
144 return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER);
145}
146
147/*
148 * Platform device handlers
149 */
150
151/*
152 * We write our info in page, we begin at offset off and cannot write more
153 * than count bytes. We set eof to 1 if we handle those 2 values. We return the
154 * number of bytes written in page
155 */
156static ssize_t show_infos(struct device *dev,
157 struct device_attribute *attr, char *page)
158{
159 int len = 0;
160 int temp;
161 char buf[16]; //enough for all info
162 /*
163 * We use the easy way, we don't care of off and count, so we don't set eof
164 * to 1
165 */
166
167 len += sprintf(page, ASUS_HOTK_NAME " " ASUS_LAPTOP_VERSION "\n");
168 len += sprintf(page + len, "Model reference : %s\n", hotk->name);
169 /*
170 * The SFUN method probably allows the original driver to get the list
171 * of features supported by a given model. For now, 0x0100 or 0x0800
172 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
173 * The significance of others is yet to be found.
174 */
175 if (read_acpi_int(hotk->handle, "SFUN", &temp, NULL))
176 len +=
177 sprintf(page + len, "SFUN value : 0x%04x\n", temp);
178 /*
179 * Another value for userspace: the ASYM method returns 0x02 for
180 * battery low and 0x04 for battery critical, its readings tend to be
181 * more accurate than those provided by _BST.
182 * Note: since not all the laptops provide this method, errors are
183 * silently ignored.
184 */
185 if (read_acpi_int(hotk->handle, "ASYM", &temp, NULL))
186 len +=
187 sprintf(page + len, "ASYM value : 0x%04x\n", temp);
188 if (asus_info) {
189 snprintf(buf, 16, "%d", asus_info->length);
190 len += sprintf(page + len, "DSDT length : %s\n", buf);
191 snprintf(buf, 16, "%d", asus_info->checksum);
192 len += sprintf(page + len, "DSDT checksum : %s\n", buf);
193 snprintf(buf, 16, "%d", asus_info->revision);
194 len += sprintf(page + len, "DSDT revision : %s\n", buf);
195 snprintf(buf, 7, "%s", asus_info->oem_id);
196 len += sprintf(page + len, "OEM id : %s\n", buf);
197 snprintf(buf, 9, "%s", asus_info->oem_table_id);
198 len += sprintf(page + len, "OEM table id : %s\n", buf);
199 snprintf(buf, 16, "%x", asus_info->oem_revision);
200 len += sprintf(page + len, "OEM revision : 0x%s\n", buf);
201 snprintf(buf, 5, "%s", asus_info->asl_compiler_id);
202 len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
203 snprintf(buf, 16, "%x", asus_info->asl_compiler_revision);
204 len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf);
205 }
206
207 return len;
208}
209
210static int parse_arg(const char *buf, unsigned long count, int *val)
211{
212 if (!count)
213 return 0;
214 if (count > 31)
215 return -EINVAL;
216 if (sscanf(buf, "%i", val) != 1)
217 return -EINVAL;
218 return count;
219}
220
221static void asus_hotk_notify(acpi_handle handle, u32 event, void *data)
222{
223 /* TODO Find a better way to handle events count. */
224 if (!hotk)
225 return;
226
227 acpi_bus_generate_event(hotk->device, event,
228 hotk->event_count[event % 128]++);
229
230 return;
231}
232
233#define ASUS_CREATE_DEVICE_ATTR(_name) \
234 struct device_attribute dev_attr_##_name = { \
235 .attr = { \
236 .name = __stringify(_name), \
237 .mode = 0, \
238 .owner = THIS_MODULE }, \
239 .show = NULL, \
240 .store = NULL, \
241 }
242
243#define ASUS_SET_DEVICE_ATTR(_name, _mode, _show, _store) \
244 do { \
245 dev_attr_##_name.attr.mode = _mode; \
246 dev_attr_##_name.show = _show; \
247 dev_attr_##_name.store = _store; \
248 } while(0)
249
250static ASUS_CREATE_DEVICE_ATTR(infos);
251
252static struct attribute *asuspf_attributes[] = {
253 &dev_attr_infos.attr,
254 NULL
255};
256
257static struct attribute_group asuspf_attribute_group = {
258 .attrs = asuspf_attributes
259};
260
261static struct platform_driver asuspf_driver = {
262 .driver = {
263 .name = ASUS_HOTK_FILE,
264 .owner = THIS_MODULE,
265 }
266};
267
268static struct platform_device *asuspf_device;
269
270
271static void asus_hotk_add_fs(void)
272{
273 ASUS_SET_DEVICE_ATTR(infos, 0444, show_infos, NULL);
274}
275
276static int asus_handle_init(char *name, acpi_handle *handle,
277 char **paths, int num_paths)
278{
279 int i;
280 acpi_status status;
281
282 for (i = 0; i < num_paths; i++) {
283 status = acpi_get_handle(NULL, paths[i], handle);
284 if (ACPI_SUCCESS(status))
285 return 0;
286 }
287
288 *handle = NULL;
289 return -ENODEV;
290}
291
292#define ASUS_HANDLE_INIT(object) \
293 asus_handle_init(#object, &object##_handle, object##_paths, \
294 ARRAY_SIZE(object##_paths))
295
296
297/*
298 * This function is used to initialize the hotk with right values. In this
299 * method, we can make all the detection we want, and modify the hotk struct
300 */
301static int asus_hotk_get_info(void)
302{
303 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
304 struct acpi_buffer dsdt = { ACPI_ALLOCATE_BUFFER, NULL };
305 union acpi_object *model = NULL;
306 int bsts_result;
307 char *string = NULL;
308 acpi_status status;
309
310 /*
311 * Get DSDT headers early enough to allow for differentiating between
312 * models, but late enough to allow acpi_bus_register_driver() to fail
313 * before doing anything ACPI-specific. Should we encounter a machine,
314 * which needs special handling (i.e. its hotkey device has a different
315 * HID), this bit will be moved. A global variable asus_info contains
316 * the DSDT header.
317 */
318 status = acpi_get_table(ACPI_TABLE_ID_DSDT, 1, &dsdt);
319 if (ACPI_FAILURE(status))
320 printk(ASUS_WARNING "Couldn't get the DSDT table header\n");
321 else
322 asus_info = dsdt.pointer;
323
324 /* We have to write 0 on init this far for all ASUS models */
325 if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) {
326 printk(ASUS_ERR "Hotkey initialization failed\n");
327 return -ENODEV;
328 }
329
330 /* This needs to be called for some laptops to init properly */
331 if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result, NULL))
332 printk(ASUS_WARNING "Error calling BSTS\n");
333 else if (bsts_result)
334 printk(ASUS_NOTICE "BSTS called, 0x%02x returned\n",
335 bsts_result);
336
337 /*
338 * Try to match the object returned by INIT to the specific model.
339 * Handle every possible object (or the lack of thereof) the DSDT
340 * writers might throw at us. When in trouble, we pass NULL to
341 * asus_model_match() and try something completely different.
342 */
343 if (buffer.pointer) {
344 model = buffer.pointer;
345 switch (model->type) {
346 case ACPI_TYPE_STRING:
347 string = model->string.pointer;
348 break;
349 case ACPI_TYPE_BUFFER:
350 string = model->buffer.pointer;
351 break;
352 default:
353 string = "";
354 break;
355 }
356 }
357 hotk->name = kstrdup(string, GFP_KERNEL);
358 if (!hotk->name)
359 return -ENOMEM;
360
361 if(*string)
362 printk(ASUS_NOTICE " %s model detected\n", string);
363
364 kfree(model);
365
366 return AE_OK;
367}
368
369static int asus_hotk_check(void)
370{
371 int result = 0;
372
373 result = acpi_bus_get_status(hotk->device);
374 if (result)
375 return result;
376
377 if (hotk->device->status.present) {
378 result = asus_hotk_get_info();
379 } else {
380 printk(ASUS_ERR "Hotkey device not present, aborting\n");
381 return -EINVAL;
382 }
383
384 return result;
385}
386
387static int asus_hotk_found;
388
389static int asus_hotk_add(struct acpi_device *device)
390{
391 acpi_status status = AE_OK;
392 int result;
393
394 if (!device)
395 return -EINVAL;
396
397 printk(ASUS_NOTICE "Asus Laptop Support version %s\n",
398 ASUS_LAPTOP_VERSION);
399
400 hotk = kmalloc(sizeof(struct asus_hotk), GFP_KERNEL);
401 if (!hotk)
402 return -ENOMEM;
403 memset(hotk, 0, sizeof(struct asus_hotk));
404
405 hotk->handle = device->handle;
406 strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME);
407 strcpy(acpi_device_class(device), ASUS_HOTK_CLASS);
408 acpi_driver_data(device) = hotk;
409 hotk->device = device;
410
411 result = asus_hotk_check();
412 if (result)
413 goto end;
414
415 asus_hotk_add_fs();
416
417 /*
418 * We install the handler, it will receive the hotk in parameter, so, we
419 * could add other data to the hotk struct
420 */
421 status = acpi_install_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
422 asus_hotk_notify, hotk);
423 if (ACPI_FAILURE(status))
424 printk(ASUS_ERR "Error installing notify handler\n");
425
426 asus_hotk_found = 1;
427
428 end:
429 if (result) {
430 kfree(hotk->name);
431 kfree(hotk);
432 }
433
434 return result;
435}
436
437static int asus_hotk_remove(struct acpi_device *device, int type)
438{
439 acpi_status status = 0;
440
441 if (!device || !acpi_driver_data(device))
442 return -EINVAL;
443
444 status = acpi_remove_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
445 asus_hotk_notify);
446 if (ACPI_FAILURE(status))
447 printk(ASUS_ERR "Error removing notify handler\n");
448
449 kfree(hotk->name);
450 kfree(hotk);
451
452 return 0;
453}
454
455static void __exit asus_laptop_exit(void)
456{
457 acpi_bus_unregister_driver(&asus_hotk_driver);
458 sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group);
459 platform_device_unregister(asuspf_device);
460 platform_driver_unregister(&asuspf_driver);
461
462 kfree(asus_info);
463}
464
465static int __init asus_laptop_init(void)
466{
467 int result;
468
469 if (acpi_disabled)
470 return -ENODEV;
471
472 if (!acpi_specific_hotkey_enabled) {
473 printk(ASUS_ERR "Using generic hotkey driver\n");
474 return -ENODEV;
475 }
476
477 result = acpi_bus_register_driver(&asus_hotk_driver);
478 if (result < 0)
479 return result;
480
481 /*
482 * This is a bit of a kludge. We only want this module loaded
483 * for ASUS systems, but there's currently no way to probe the
484 * ACPI namespace for ASUS HIDs. So we just return failure if
485 * we didn't find one, which will cause the module to be
486 * unloaded.
487 */
488 if (!asus_hotk_found) {
489 acpi_bus_unregister_driver(&asus_hotk_driver);
490 return -ENODEV;
491 }
492
493 /* Register platform stuff */
494 result = platform_driver_register(&asuspf_driver);
495 if (result)
496 goto fail_platform_driver;
497
498 asuspf_device = platform_device_alloc(ASUS_HOTK_FILE, -1);
499 if (!asuspf_device) {
500 result = -ENOMEM;
501 goto fail_platform_device1;
502 }
503
504 result = platform_device_add(asuspf_device);
505 if (result)
506 goto fail_platform_device2;
507
508 result = sysfs_create_group(&asuspf_device->dev.kobj,
509 &asuspf_attribute_group);
510 if (result)
511 goto fail_sysfs;
512
513 return 0;
514
515fail_sysfs:
516 platform_device_del(asuspf_device);
517
518fail_platform_device2:
519 platform_device_put(asuspf_device);
520
521fail_platform_device1:
522 platform_driver_unregister(&asuspf_driver);
523
524fail_platform_driver:
525
526 return result;
527}
528
529module_init(asus_laptop_init);
530module_exit(asus_laptop_exit);