aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLen Brown <len.brown@intel.com>2007-02-03 01:38:30 -0500
committerLen Brown <len.brown@intel.com>2007-02-03 01:38:30 -0500
commita4144e451ce1951e8dad2aa8e2288f75ab745172 (patch)
tree133ea66d3f685702c0a628e5001d86ca2ed393db
parenteee3c859c486d4f110f154807430eaf825ff4a3d (diff)
parent8def05fa82bfa4af0c8e83a00ff377ddd9074480 (diff)
Pull asus into test branch
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/acpi/Kconfig13
-rw-r--r--drivers/misc/Kconfig19
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/asus-laptop.c1170
5 files changed, 1206 insertions, 5 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 0ad8803a0c75..efca26a9242c 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 5c087a5bddf8..2ac2922be1bf 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -207,19 +207,22 @@ config ACPI_ASUS
207 207
208 Note: display switching code is currently considered EXPERIMENTAL, 208 Note: display switching code is currently considered EXPERIMENTAL,
209 toying with these values may even lock your machine. 209 toying with these values may even lock your machine.
210 210
211 All settings are changed via /proc/acpi/asus directory entries. Owner 211 All settings are changed via /proc/acpi/asus directory entries. Owner
212 and group for these entries can be set with asus_uid and asus_gid 212 and group for these entries can be set with asus_uid and asus_gid
213 parameters. 213 parameters.
214 214
215 More information and a userspace daemon for handling the extra buttons 215 More information and a userspace daemon for handling the extra buttons
216 at <http://sourceforge.net/projects/acpi4asus/>. 216 at <http://sourceforge.net/projects/acpi4asus/>.
217 217
218 If you have an ACPI-compatible ASUS laptop, say Y or M here. This 218 If you have an ACPI-compatible ASUS laptop, say Y or M here. This
219 driver is still under development, so if your laptop is unsupported or 219 driver is still under development, so if your laptop is unsupported or
220 something works not quite as expected, please use the mailing list 220 something works not quite as expected, please use the mailing list
221 available on the above page (acpi4asus-user@lists.sourceforge.net) 221 available on the above page (acpi4asus-user@lists.sourceforge.net).
222 222
223 NOTE: This driver is deprecated and will probably be removed soon,
224 use asus-laptop instead.
225
223config ACPI_IBM 226config ACPI_IBM
224 tristate "IBM ThinkPad Laptop Extras" 227 tristate "IBM ThinkPad Laptop Extras"
225 depends on X86 228 depends on X86
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 00db31c314e0..89bba277da5f 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -69,6 +69,25 @@ 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 depends on LEDS_CLASS
78 depends on BACKLIGHT_CLASS_DEVICE
79 ---help---
80 This is the new Linux driver for Asus laptops. It may also support some
81 MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate
82 standard ACPI events that go through /proc/acpi/events. It also adds
83 support for video output switching, LCD backlight control, Bluetooth and
84 Wlan control, and most importantly, allows you to blink those fancy LEDs.
85
86 For more information and a userspace daemon for handling the extra
87 buttons see <http://acpi4asus.sf.net/>.
88
89 If you have an ACPI-compatible ASUS laptop, say Y or M here.
90
72config MSI_LAPTOP 91config MSI_LAPTOP
73 tristate "MSI Laptop Extras" 92 tristate "MSI Laptop Extras"
74 depends on X86 93 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..b6243506add8
--- /dev/null
+++ b/drivers/misc/asus-laptop.c
@@ -0,0 +1,1170 @@
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/backlight.h>
44#include <linux/fb.h>
45#include <linux/leds.h>
46#include <linux/platform_device.h>
47#include <acpi/acpi_drivers.h>
48#include <acpi/acpi_bus.h>
49#include <asm/uaccess.h>
50
51#define ASUS_LAPTOP_VERSION "0.40"
52
53#define ASUS_HOTK_NAME "Asus Laptop Support"
54#define ASUS_HOTK_CLASS "hotkey"
55#define ASUS_HOTK_DEVICE_NAME "Hotkey"
56#define ASUS_HOTK_HID "ATK0100"
57#define ASUS_HOTK_FILE "asus-laptop"
58#define ASUS_HOTK_PREFIX "\\_SB.ATKD."
59
60/*
61 * Some events we use, same for all Asus
62 */
63#define ATKD_BR_UP 0x10
64#define ATKD_BR_DOWN 0x20
65#define ATKD_LCD_ON 0x33
66#define ATKD_LCD_OFF 0x34
67
68/*
69 * Known bits returned by \_SB.ATKD.HWRS
70 */
71#define WL_HWRS 0x80
72#define BT_HWRS 0x100
73
74/*
75 * Flags for hotk status
76 * WL_ON and BT_ON are also used for wireless_status()
77 */
78#define WL_ON 0x01 //internal Wifi
79#define BT_ON 0x02 //internal Bluetooth
80#define MLED_ON 0x04 //mail LED
81#define TLED_ON 0x08 //touchpad LED
82#define RLED_ON 0x10 //Record LED
83#define PLED_ON 0x20 //Phone LED
84#define LCD_ON 0x40 //LCD backlight
85
86#define ASUS_LOG ASUS_HOTK_FILE ": "
87#define ASUS_ERR KERN_ERR ASUS_LOG
88#define ASUS_WARNING KERN_WARNING ASUS_LOG
89#define ASUS_NOTICE KERN_NOTICE ASUS_LOG
90#define ASUS_INFO KERN_INFO ASUS_LOG
91#define ASUS_DEBUG KERN_DEBUG ASUS_LOG
92
93MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
94MODULE_DESCRIPTION(ASUS_HOTK_NAME);
95MODULE_LICENSE("GPL");
96
97#define ASUS_HANDLE(object, paths...) \
98 static acpi_handle object##_handle = NULL; \
99 static char *object##_paths[] = { paths }
100
101/* LED */
102ASUS_HANDLE(mled_set, ASUS_HOTK_PREFIX "MLED");
103ASUS_HANDLE(tled_set, ASUS_HOTK_PREFIX "TLED");
104ASUS_HANDLE(rled_set, ASUS_HOTK_PREFIX "RLED"); /* W1JC */
105ASUS_HANDLE(pled_set, ASUS_HOTK_PREFIX "PLED"); /* A7J */
106
107/* LEDD */
108ASUS_HANDLE(ledd_set, ASUS_HOTK_PREFIX "SLCM");
109
110/* Bluetooth and WLAN
111 * WLED and BLED are not handled like other XLED, because in some dsdt
112 * they also control the WLAN/Bluetooth device.
113 */
114ASUS_HANDLE(wl_switch, ASUS_HOTK_PREFIX "WLED");
115ASUS_HANDLE(bt_switch, ASUS_HOTK_PREFIX "BLED");
116ASUS_HANDLE(wireless_status, ASUS_HOTK_PREFIX "RSTS"); /* All new models */
117
118/* Brightness */
119ASUS_HANDLE(brightness_set, ASUS_HOTK_PREFIX "SPLV");
120ASUS_HANDLE(brightness_get, ASUS_HOTK_PREFIX "GPLV");
121
122/* Backlight */
123ASUS_HANDLE(lcd_switch, "\\_SB.PCI0.SBRG.EC0._Q10", /* All new models */
124 "\\_SB.PCI0.ISA.EC0._Q10", /* A1x */
125 "\\_SB.PCI0.PX40.ECD0._Q10", /* L3C */
126 "\\_SB.PCI0.PX40.EC0.Q10", /* M1A */
127 "\\_SB.PCI0.LPCB.EC0._Q10", /* P30 */
128 "\\_SB.PCI0.PX40.Q10", /* S1x */
129 "\\Q10"); /* A2x, L2D, L3D, M2E */
130
131/* Display */
132ASUS_HANDLE(display_set, ASUS_HOTK_PREFIX "SDSP");
133ASUS_HANDLE(display_get, "\\_SB.PCI0.P0P1.VGA.GETD", /* A6B, A6K A6R A7D F3JM L4R M6R A3G
134 M6A M6V VX-1 V6J V6V W3Z */
135 "\\_SB.PCI0.P0P2.VGA.GETD", /* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V
136 S5A M5A z33A W1Jc W2V */
137 "\\_SB.PCI0.P0P3.VGA.GETD", /* A6V A6Q */
138 "\\_SB.PCI0.P0PA.VGA.GETD", /* A6T, A6M */
139 "\\_SB.PCI0.PCI1.VGAC.NMAP", /* L3C */
140 "\\_SB.PCI0.VGA.GETD", /* Z96F */
141 "\\ACTD", /* A2D */
142 "\\ADVG", /* A4G Z71A W1N W5A W5F M2N M3N M5N M6N S1N S5N */
143 "\\DNXT", /* P30 */
144 "\\INFB", /* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */
145 "\\SSTE"); /* A3F A6F A3N A3L M6N W3N W6A */
146
147ASUS_HANDLE(ls_switch, ASUS_HOTK_PREFIX "ALSC"); /* Z71A Z71V */
148ASUS_HANDLE(ls_level, ASUS_HOTK_PREFIX "ALSL"); /* Z71A Z71V */
149
150/*
151 * This is the main structure, we can use it to store anything interesting
152 * about the hotk device
153 */
154struct asus_hotk {
155 char *name; //laptop name
156 struct acpi_device *device; //the device we are in
157 acpi_handle handle; //the handle of the hotk device
158 char status; //status of the hotk, for LEDs, ...
159 u32 ledd_status; //status of the LED display
160 u8 light_level; //light sensor level
161 u8 light_switch; //light sensor switch value
162 u16 event_count[128]; //count for each event TODO make this better
163};
164
165/*
166 * This header is made available to allow proper configuration given model,
167 * revision number , ... this info cannot go in struct asus_hotk because it is
168 * available before the hotk
169 */
170static struct acpi_table_header *asus_info;
171
172/* The actual device the driver binds to */
173static struct asus_hotk *hotk;
174
175/*
176 * The hotkey driver declaration
177 */
178static int asus_hotk_add(struct acpi_device *device);
179static int asus_hotk_remove(struct acpi_device *device, int type);
180static struct acpi_driver asus_hotk_driver = {
181 .name = ASUS_HOTK_NAME,
182 .class = ASUS_HOTK_CLASS,
183 .ids = ASUS_HOTK_HID,
184 .ops = {
185 .add = asus_hotk_add,
186 .remove = asus_hotk_remove,
187 },
188};
189
190/* The backlight device /sys/class/backlight */
191static struct backlight_device *asus_backlight_device;
192
193/*
194 * The backlight class declaration
195 */
196static int read_brightness(struct backlight_device *bd);
197static int update_bl_status(struct backlight_device *bd);
198static struct backlight_properties asusbl_data = {
199 .owner = THIS_MODULE,
200 .get_brightness = read_brightness,
201 .update_status = update_bl_status,
202 .max_brightness = 15,
203};
204
205/* These functions actually update the LED's, and are called from a
206 * workqueue. By doing this as separate work rather than when the LED
207 * subsystem asks, we avoid messing with the Asus ACPI stuff during a
208 * potentially bad time, such as a timer interrupt. */
209static struct workqueue_struct *led_workqueue;
210
211#define ASUS_LED(object, ledname) \
212 static void object##_led_set(struct led_classdev *led_cdev, \
213 enum led_brightness value); \
214 static void object##_led_update(struct work_struct *ignored); \
215 static int object##_led_wk; \
216 DECLARE_WORK(object##_led_work, object##_led_update); \
217 static struct led_classdev object##_led = { \
218 .name = "asus:" ledname, \
219 .brightness_set = object##_led_set, \
220 }
221
222ASUS_LED(mled, "mail");
223ASUS_LED(tled, "touchpad");
224ASUS_LED(rled, "record");
225ASUS_LED(pled, "phone");
226
227/*
228 * This function evaluates an ACPI method, given an int as parameter, the
229 * method is searched within the scope of the handle, can be NULL. The output
230 * of the method is written is output, which can also be NULL
231 *
232 * returns 1 if write is successful, 0 else.
233 */
234static int write_acpi_int(acpi_handle handle, const char *method, int val,
235 struct acpi_buffer *output)
236{
237 struct acpi_object_list params; //list of input parameters (an int here)
238 union acpi_object in_obj; //the only param we use
239 acpi_status status;
240
241 params.count = 1;
242 params.pointer = &in_obj;
243 in_obj.type = ACPI_TYPE_INTEGER;
244 in_obj.integer.value = val;
245
246 status = acpi_evaluate_object(handle, (char *)method, &params, output);
247 return (status == AE_OK);
248}
249
250static int read_acpi_int(acpi_handle handle, const char *method, int *val,
251 struct acpi_object_list *params)
252{
253 struct acpi_buffer output;
254 union acpi_object out_obj;
255 acpi_status status;
256
257 output.length = sizeof(out_obj);
258 output.pointer = &out_obj;
259
260 status = acpi_evaluate_object(handle, (char *)method, params, &output);
261 *val = out_obj.integer.value;
262 return (status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER);
263}
264
265static int read_wireless_status(int mask)
266{
267 int status;
268
269 if (!wireless_status_handle)
270 return (hotk->status & mask) ? 1 : 0;
271
272 if (read_acpi_int(wireless_status_handle, NULL, &status, NULL)) {
273 return (status & mask) ? 1 : 0;
274 } else
275 printk(ASUS_WARNING "Error reading Wireless status\n");
276
277 return (hotk->status & mask) ? 1 : 0;
278}
279
280/* Generic LED functions */
281static int read_status(int mask)
282{
283 /* There is a special method for both wireless devices */
284 if (mask == BT_ON || mask == WL_ON)
285 return read_wireless_status(mask);
286
287 return (hotk->status & mask) ? 1 : 0;
288}
289
290static void write_status(acpi_handle handle, int out, int mask, int invert)
291{
292 hotk->status = (out) ? (hotk->status | mask) : (hotk->status & ~mask);
293
294 if (invert) /* invert target value */
295 out = !out & 0x1;
296
297 if (handle && !write_acpi_int(handle, NULL, out, NULL))
298 printk(ASUS_WARNING " write failed\n");
299}
300
301/* /sys/class/led handlers */
302#define ASUS_LED_HANDLER(object, mask, invert) \
303 static void object##_led_set(struct led_classdev *led_cdev, \
304 enum led_brightness value) \
305 { \
306 object##_led_wk = value; \
307 queue_work(led_workqueue, &object##_led_work); \
308 } \
309 static void object##_led_update(struct work_struct *ignored) \
310 { \
311 int value = object##_led_wk; \
312 write_status(object##_set_handle, value, (mask), (invert)); \
313 }
314
315ASUS_LED_HANDLER(mled, MLED_ON, 1);
316ASUS_LED_HANDLER(pled, PLED_ON, 0);
317ASUS_LED_HANDLER(rled, RLED_ON, 0);
318ASUS_LED_HANDLER(tled, TLED_ON, 0);
319
320static int get_lcd_state(void)
321{
322 return read_status(LCD_ON);
323}
324
325static int set_lcd_state(int value)
326{
327 int lcd = 0;
328 acpi_status status = 0;
329
330 lcd = value ? 1 : 0;
331
332 if (lcd == get_lcd_state())
333 return 0;
334
335 if (lcd_switch_handle) {
336 status = acpi_evaluate_object(lcd_switch_handle,
337 NULL, NULL, NULL);
338
339 if (ACPI_FAILURE(status))
340 printk(ASUS_WARNING "Error switching LCD\n");
341 }
342
343 write_status(NULL, lcd, LCD_ON, 0);
344 return 0;
345}
346
347static void lcd_blank(int blank)
348{
349 struct backlight_device *bd = asus_backlight_device;
350
351 if (bd) {
352 down(&bd->sem);
353 if (likely(bd->props)) {
354 bd->props->power = blank;
355 if (likely(bd->props->update_status))
356 bd->props->update_status(bd);
357 }
358 up(&bd->sem);
359 }
360}
361
362static int read_brightness(struct backlight_device *bd)
363{
364 int value;
365
366 if (!read_acpi_int(brightness_get_handle, NULL, &value, NULL))
367 printk(ASUS_WARNING "Error reading brightness\n");
368
369 return value;
370}
371
372static int set_brightness(struct backlight_device *bd, int value)
373{
374 int ret = 0;
375
376 value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
377 /* 0 <= value <= 15 */
378
379 if (!write_acpi_int(brightness_set_handle, NULL, value, NULL)) {
380 printk(ASUS_WARNING "Error changing brightness\n");
381 ret = -EIO;
382 }
383
384 return ret;
385}
386
387static int update_bl_status(struct backlight_device *bd)
388{
389 int rv;
390 int value = bd->props->brightness;
391
392 rv = set_brightness(bd, value);
393 if (rv)
394 return rv;
395
396 value = (bd->props->power == FB_BLANK_UNBLANK) ? 1 : 0;
397 return set_lcd_state(value);
398}
399
400/*
401 * Platform device handlers
402 */
403
404/*
405 * We write our info in page, we begin at offset off and cannot write more
406 * than count bytes. We set eof to 1 if we handle those 2 values. We return the
407 * number of bytes written in page
408 */
409static ssize_t show_infos(struct device *dev,
410 struct device_attribute *attr, char *page)
411{
412 int len = 0;
413 int temp;
414 char buf[16]; //enough for all info
415 /*
416 * We use the easy way, we don't care of off and count, so we don't set eof
417 * to 1
418 */
419
420 len += sprintf(page, ASUS_HOTK_NAME " " ASUS_LAPTOP_VERSION "\n");
421 len += sprintf(page + len, "Model reference : %s\n", hotk->name);
422 /*
423 * The SFUN method probably allows the original driver to get the list
424 * of features supported by a given model. For now, 0x0100 or 0x0800
425 * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
426 * The significance of others is yet to be found.
427 */
428 if (read_acpi_int(hotk->handle, "SFUN", &temp, NULL))
429 len +=
430 sprintf(page + len, "SFUN value : 0x%04x\n", temp);
431 /*
432 * Another value for userspace: the ASYM method returns 0x02 for
433 * battery low and 0x04 for battery critical, its readings tend to be
434 * more accurate than those provided by _BST.
435 * Note: since not all the laptops provide this method, errors are
436 * silently ignored.
437 */
438 if (read_acpi_int(hotk->handle, "ASYM", &temp, NULL))
439 len +=
440 sprintf(page + len, "ASYM value : 0x%04x\n", temp);
441 if (asus_info) {
442 snprintf(buf, 16, "%d", asus_info->length);
443 len += sprintf(page + len, "DSDT length : %s\n", buf);
444 snprintf(buf, 16, "%d", asus_info->checksum);
445 len += sprintf(page + len, "DSDT checksum : %s\n", buf);
446 snprintf(buf, 16, "%d", asus_info->revision);
447 len += sprintf(page + len, "DSDT revision : %s\n", buf);
448 snprintf(buf, 7, "%s", asus_info->oem_id);
449 len += sprintf(page + len, "OEM id : %s\n", buf);
450 snprintf(buf, 9, "%s", asus_info->oem_table_id);
451 len += sprintf(page + len, "OEM table id : %s\n", buf);
452 snprintf(buf, 16, "%x", asus_info->oem_revision);
453 len += sprintf(page + len, "OEM revision : 0x%s\n", buf);
454 snprintf(buf, 5, "%s", asus_info->asl_compiler_id);
455 len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
456 snprintf(buf, 16, "%x", asus_info->asl_compiler_revision);
457 len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf);
458 }
459
460 return len;
461}
462
463static int parse_arg(const char *buf, unsigned long count, int *val)
464{
465 if (!count)
466 return 0;
467 if (count > 31)
468 return -EINVAL;
469 if (sscanf(buf, "%i", val) != 1)
470 return -EINVAL;
471 return count;
472}
473
474static ssize_t store_status(const char *buf, size_t count,
475 acpi_handle handle, int mask, int invert)
476{
477 int rv, value;
478 int out = 0;
479
480 rv = parse_arg(buf, count, &value);
481 if (rv > 0)
482 out = value ? 1 : 0;
483
484 write_status(handle, out, mask, invert);
485
486 return rv;
487}
488
489/*
490 * LEDD display
491 */
492static ssize_t show_ledd(struct device *dev,
493 struct device_attribute *attr, char *buf)
494{
495 return sprintf(buf, "0x%08x\n", hotk->ledd_status);
496}
497
498static ssize_t store_ledd(struct device *dev, struct device_attribute *attr,
499 const char *buf, size_t count)
500{
501 int rv, value;
502
503 rv = parse_arg(buf, count, &value);
504 if (rv > 0) {
505 if (!write_acpi_int(ledd_set_handle, NULL, value, NULL))
506 printk(ASUS_WARNING "LED display write failed\n");
507 else
508 hotk->ledd_status = (u32) value;
509 }
510 return rv;
511}
512
513/*
514 * WLAN
515 */
516static ssize_t show_wlan(struct device *dev,
517 struct device_attribute *attr, char *buf)
518{
519 return sprintf(buf, "%d\n", read_status(WL_ON));
520}
521
522static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,
523 const char *buf, size_t count)
524{
525 return store_status(buf, count, wl_switch_handle, WL_ON, 0);
526}
527
528/*
529 * Bluetooth
530 */
531static ssize_t show_bluetooth(struct device *dev,
532 struct device_attribute *attr, char *buf)
533{
534 return sprintf(buf, "%d\n", read_status(BT_ON));
535}
536
537static ssize_t store_bluetooth(struct device *dev,
538 struct device_attribute *attr, const char *buf,
539 size_t count)
540{
541 return store_status(buf, count, bt_switch_handle, BT_ON, 0);
542}
543
544/*
545 * Display
546 */
547static void set_display(int value)
548{
549 /* no sanity check needed for now */
550 if (!write_acpi_int(display_set_handle, NULL, value, NULL))
551 printk(ASUS_WARNING "Error setting display\n");
552 return;
553}
554
555static int read_display(void)
556{
557 int value = 0;
558
559 /* In most of the case, we know how to set the display, but sometime
560 we can't read it */
561 if (display_get_handle) {
562 if (!read_acpi_int(display_get_handle, NULL, &value, NULL))
563 printk(ASUS_WARNING "Error reading display status\n");
564 }
565
566 value &= 0x0F; /* needed for some models, shouldn't hurt others */
567
568 return value;
569}
570
571/*
572 * Now, *this* one could be more user-friendly, but so far, no-one has
573 * complained. The significance of bits is the same as in store_disp()
574 */
575static ssize_t show_disp(struct device *dev,
576 struct device_attribute *attr, char *buf)
577{
578 return sprintf(buf, "%d\n", read_display());
579}
580
581/*
582 * Experimental support for display switching. As of now: 1 should activate
583 * the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI.
584 * Any combination (bitwise) of these will suffice. I never actually tested 4
585 * displays hooked up simultaneously, so be warned. See the acpi4asus README
586 * for more info.
587 */
588static ssize_t store_disp(struct device *dev, struct device_attribute *attr,
589 const char *buf, size_t count)
590{
591 int rv, value;
592
593 rv = parse_arg(buf, count, &value);
594 if (rv > 0)
595 set_display(value);
596 return rv;
597}
598
599/*
600 * Light Sens
601 */
602static void set_light_sens_switch(int value)
603{
604 if (!write_acpi_int(ls_switch_handle, NULL, value, NULL))
605 printk(ASUS_WARNING "Error setting light sensor switch\n");
606 hotk->light_switch = value;
607}
608
609static ssize_t show_lssw(struct device *dev,
610 struct device_attribute *attr, char *buf)
611{
612 return sprintf(buf, "%d\n", hotk->light_switch);
613}
614
615static ssize_t store_lssw(struct device *dev, struct device_attribute *attr,
616 const char *buf, size_t count)
617{
618 int rv, value;
619
620 rv = parse_arg(buf, count, &value);
621 if (rv > 0)
622 set_light_sens_switch(value ? 1 : 0);
623
624 return rv;
625}
626
627static void set_light_sens_level(int value)
628{
629 if (!write_acpi_int(ls_level_handle, NULL, value, NULL))
630 printk(ASUS_WARNING "Error setting light sensor level\n");
631 hotk->light_level = value;
632}
633
634static ssize_t show_lslvl(struct device *dev,
635 struct device_attribute *attr, char *buf)
636{
637 return sprintf(buf, "%d\n", hotk->light_level);
638}
639
640static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr,
641 const char *buf, size_t count)
642{
643 int rv, value;
644
645 rv = parse_arg(buf, count, &value);
646 if (rv > 0) {
647 value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
648 /* 0 <= value <= 15 */
649 set_light_sens_level(value);
650 }
651
652 return rv;
653}
654
655static void asus_hotk_notify(acpi_handle handle, u32 event, void *data)
656{
657 /* TODO Find a better way to handle events count. */
658 if (!hotk)
659 return;
660
661 /*
662 * We need to tell the backlight device when the backlight power is
663 * switched
664 */
665 if (event == ATKD_LCD_ON) {
666 write_status(NULL, 1, LCD_ON, 0);
667 lcd_blank(FB_BLANK_UNBLANK);
668 } else if (event == ATKD_LCD_OFF) {
669 write_status(NULL, 0, LCD_ON, 0);
670 lcd_blank(FB_BLANK_POWERDOWN);
671 }
672
673 acpi_bus_generate_event(hotk->device, event,
674 hotk->event_count[event % 128]++);
675
676 return;
677}
678
679#define ASUS_CREATE_DEVICE_ATTR(_name) \
680 struct device_attribute dev_attr_##_name = { \
681 .attr = { \
682 .name = __stringify(_name), \
683 .mode = 0, \
684 .owner = THIS_MODULE }, \
685 .show = NULL, \
686 .store = NULL, \
687 }
688
689#define ASUS_SET_DEVICE_ATTR(_name, _mode, _show, _store) \
690 do { \
691 dev_attr_##_name.attr.mode = _mode; \
692 dev_attr_##_name.show = _show; \
693 dev_attr_##_name.store = _store; \
694 } while(0)
695
696static ASUS_CREATE_DEVICE_ATTR(infos);
697static ASUS_CREATE_DEVICE_ATTR(wlan);
698static ASUS_CREATE_DEVICE_ATTR(bluetooth);
699static ASUS_CREATE_DEVICE_ATTR(display);
700static ASUS_CREATE_DEVICE_ATTR(ledd);
701static ASUS_CREATE_DEVICE_ATTR(ls_switch);
702static ASUS_CREATE_DEVICE_ATTR(ls_level);
703
704static struct attribute *asuspf_attributes[] = {
705 &dev_attr_infos.attr,
706 &dev_attr_wlan.attr,
707 &dev_attr_bluetooth.attr,
708 &dev_attr_display.attr,
709 &dev_attr_ledd.attr,
710 &dev_attr_ls_switch.attr,
711 &dev_attr_ls_level.attr,
712 NULL
713};
714
715static struct attribute_group asuspf_attribute_group = {
716 .attrs = asuspf_attributes
717};
718
719static struct platform_driver asuspf_driver = {
720 .driver = {
721 .name = ASUS_HOTK_FILE,
722 .owner = THIS_MODULE,
723 }
724};
725
726static struct platform_device *asuspf_device;
727
728static void asus_hotk_add_fs(void)
729{
730 ASUS_SET_DEVICE_ATTR(infos, 0444, show_infos, NULL);
731
732 if (wl_switch_handle)
733 ASUS_SET_DEVICE_ATTR(wlan, 0644, show_wlan, store_wlan);
734
735 if (bt_switch_handle)
736 ASUS_SET_DEVICE_ATTR(bluetooth, 0644,
737 show_bluetooth, store_bluetooth);
738
739 if (display_set_handle && display_get_handle)
740 ASUS_SET_DEVICE_ATTR(display, 0644, show_disp, store_disp);
741 else if (display_set_handle)
742 ASUS_SET_DEVICE_ATTR(display, 0200, NULL, store_disp);
743
744 if (ledd_set_handle)
745 ASUS_SET_DEVICE_ATTR(ledd, 0644, show_ledd, store_ledd);
746
747 if (ls_switch_handle && ls_level_handle) {
748 ASUS_SET_DEVICE_ATTR(ls_level, 0644, show_lslvl, store_lslvl);
749 ASUS_SET_DEVICE_ATTR(ls_switch, 0644, show_lssw, store_lssw);
750 }
751}
752
753static int asus_handle_init(char *name, acpi_handle * handle,
754 char **paths, int num_paths)
755{
756 int i;
757 acpi_status status;
758
759 for (i = 0; i < num_paths; i++) {
760 status = acpi_get_handle(NULL, paths[i], handle);
761 if (ACPI_SUCCESS(status))
762 return 0;
763 }
764
765 *handle = NULL;
766 return -ENODEV;
767}
768
769#define ASUS_HANDLE_INIT(object) \
770 asus_handle_init(#object, &object##_handle, object##_paths, \
771 ARRAY_SIZE(object##_paths))
772
773/*
774 * This function is used to initialize the hotk with right values. In this
775 * method, we can make all the detection we want, and modify the hotk struct
776 */
777static int asus_hotk_get_info(void)
778{
779 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
780 struct acpi_buffer dsdt = { ACPI_ALLOCATE_BUFFER, NULL };
781 union acpi_object *model = NULL;
782 int bsts_result, hwrs_result;
783 char *string = NULL;
784 acpi_status status;
785
786 /*
787 * Get DSDT headers early enough to allow for differentiating between
788 * models, but late enough to allow acpi_bus_register_driver() to fail
789 * before doing anything ACPI-specific. Should we encounter a machine,
790 * which needs special handling (i.e. its hotkey device has a different
791 * HID), this bit will be moved. A global variable asus_info contains
792 * the DSDT header.
793 */
794 status = acpi_get_table(ACPI_TABLE_ID_DSDT, 1, &dsdt);
795 if (ACPI_FAILURE(status))
796 printk(ASUS_WARNING "Couldn't get the DSDT table header\n");
797 else
798 asus_info = dsdt.pointer;
799
800 /* We have to write 0 on init this far for all ASUS models */
801 if (!write_acpi_int(hotk->handle, "INIT", 0, &buffer)) {
802 printk(ASUS_ERR "Hotkey initialization failed\n");
803 return -ENODEV;
804 }
805
806 /* This needs to be called for some laptops to init properly */
807 if (!read_acpi_int(hotk->handle, "BSTS", &bsts_result, NULL))
808 printk(ASUS_WARNING "Error calling BSTS\n");
809 else if (bsts_result)
810 printk(ASUS_NOTICE "BSTS called, 0x%02x returned\n",
811 bsts_result);
812
813 /*
814 * Try to match the object returned by INIT to the specific model.
815 * Handle every possible object (or the lack of thereof) the DSDT
816 * writers might throw at us. When in trouble, we pass NULL to
817 * asus_model_match() and try something completely different.
818 */
819 if (buffer.pointer) {
820 model = buffer.pointer;
821 switch (model->type) {
822 case ACPI_TYPE_STRING:
823 string = model->string.pointer;
824 break;
825 case ACPI_TYPE_BUFFER:
826 string = model->buffer.pointer;
827 break;
828 default:
829 string = "";
830 break;
831 }
832 }
833 hotk->name = kstrdup(string, GFP_KERNEL);
834 if (!hotk->name)
835 return -ENOMEM;
836
837 if (*string)
838 printk(ASUS_NOTICE " %s model detected\n", string);
839
840 ASUS_HANDLE_INIT(mled_set);
841 ASUS_HANDLE_INIT(tled_set);
842 ASUS_HANDLE_INIT(rled_set);
843 ASUS_HANDLE_INIT(pled_set);
844
845 ASUS_HANDLE_INIT(ledd_set);
846
847 /*
848 * The HWRS method return informations about the hardware.
849 * 0x80 bit is for WLAN, 0x100 for Bluetooth.
850 * The significance of others is yet to be found.
851 * If we don't find the method, we assume the device are present.
852 */
853 if (!read_acpi_int(hotk->handle, "HRWS", &hwrs_result, NULL))
854 hwrs_result = WL_HWRS | BT_HWRS;
855
856 if (hwrs_result & WL_HWRS)
857 ASUS_HANDLE_INIT(wl_switch);
858 if (hwrs_result & BT_HWRS)
859 ASUS_HANDLE_INIT(bt_switch);
860
861 ASUS_HANDLE_INIT(wireless_status);
862
863 ASUS_HANDLE_INIT(brightness_set);
864 ASUS_HANDLE_INIT(brightness_get);
865
866 ASUS_HANDLE_INIT(lcd_switch);
867
868 ASUS_HANDLE_INIT(display_set);
869 ASUS_HANDLE_INIT(display_get);
870
871 /* There is a lot of models with "ALSL", but a few get
872 a real light sens, so we need to check it. */
873 if (ASUS_HANDLE_INIT(ls_switch))
874 ASUS_HANDLE_INIT(ls_level);
875
876 kfree(model);
877
878 return AE_OK;
879}
880
881static int asus_hotk_check(void)
882{
883 int result = 0;
884
885 result = acpi_bus_get_status(hotk->device);
886 if (result)
887 return result;
888
889 if (hotk->device->status.present) {
890 result = asus_hotk_get_info();
891 } else {
892 printk(ASUS_ERR "Hotkey device not present, aborting\n");
893 return -EINVAL;
894 }
895
896 return result;
897}
898
899static int asus_hotk_found;
900
901static int asus_hotk_add(struct acpi_device *device)
902{
903 acpi_status status = AE_OK;
904 int result;
905
906 if (!device)
907 return -EINVAL;
908
909 printk(ASUS_NOTICE "Asus Laptop Support version %s\n",
910 ASUS_LAPTOP_VERSION);
911
912 hotk = kmalloc(sizeof(struct asus_hotk), GFP_KERNEL);
913 if (!hotk)
914 return -ENOMEM;
915 memset(hotk, 0, sizeof(struct asus_hotk));
916
917 hotk->handle = device->handle;
918 strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME);
919 strcpy(acpi_device_class(device), ASUS_HOTK_CLASS);
920 acpi_driver_data(device) = hotk;
921 hotk->device = device;
922
923 result = asus_hotk_check();
924 if (result)
925 goto end;
926
927 asus_hotk_add_fs();
928
929 /*
930 * We install the handler, it will receive the hotk in parameter, so, we
931 * could add other data to the hotk struct
932 */
933 status = acpi_install_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
934 asus_hotk_notify, hotk);
935 if (ACPI_FAILURE(status))
936 printk(ASUS_ERR "Error installing notify handler\n");
937
938 asus_hotk_found = 1;
939
940 /* WLED and BLED are on by default */
941 write_status(bt_switch_handle, 1, BT_ON, 0);
942 write_status(wl_switch_handle, 1, WL_ON, 0);
943
944 /* LCD Backlight is on by default */
945 write_status(NULL, 1, LCD_ON, 0);
946
947 /* LED display is off by default */
948 hotk->ledd_status = 0xFFF;
949
950 /* Set initial values of light sensor and level */
951 hotk->light_switch = 1; /* Default to light sensor disabled */
952 hotk->light_level = 0; /* level 5 for sensor sensitivity */
953
954 if (ls_switch_handle)
955 set_light_sens_switch(hotk->light_switch);
956
957 if (ls_level_handle)
958 set_light_sens_level(hotk->light_level);
959
960 end:
961 if (result) {
962 kfree(hotk->name);
963 kfree(hotk);
964 }
965
966 return result;
967}
968
969static int asus_hotk_remove(struct acpi_device *device, int type)
970{
971 acpi_status status = 0;
972
973 if (!device || !acpi_driver_data(device))
974 return -EINVAL;
975
976 status = acpi_remove_notify_handler(hotk->handle, ACPI_SYSTEM_NOTIFY,
977 asus_hotk_notify);
978 if (ACPI_FAILURE(status))
979 printk(ASUS_ERR "Error removing notify handler\n");
980
981 kfree(hotk->name);
982 kfree(hotk);
983
984 return 0;
985}
986
987static void asus_backlight_exit(void)
988{
989 if (asus_backlight_device)
990 backlight_device_unregister(asus_backlight_device);
991}
992
993#define ASUS_LED_UNREGISTER(object) \
994 if(object##_led.class_dev \
995 && !IS_ERR(object##_led.class_dev)) \
996 led_classdev_unregister(&object##_led)
997
998static void asus_led_exit(void)
999{
1000 ASUS_LED_UNREGISTER(mled);
1001 ASUS_LED_UNREGISTER(tled);
1002 ASUS_LED_UNREGISTER(pled);
1003 ASUS_LED_UNREGISTER(rled);
1004
1005 destroy_workqueue(led_workqueue);
1006}
1007
1008static void __exit asus_laptop_exit(void)
1009{
1010 asus_backlight_exit();
1011 asus_led_exit();
1012
1013 acpi_bus_unregister_driver(&asus_hotk_driver);
1014 sysfs_remove_group(&asuspf_device->dev.kobj, &asuspf_attribute_group);
1015 platform_device_unregister(asuspf_device);
1016 platform_driver_unregister(&asuspf_driver);
1017
1018 kfree(asus_info);
1019}
1020
1021static int asus_backlight_init(struct device *dev)
1022{
1023 struct backlight_device *bd;
1024
1025 if (brightness_set_handle && lcd_switch_handle) {
1026 bd = backlight_device_register(ASUS_HOTK_FILE, dev,
1027 NULL, &asusbl_data);
1028 if (IS_ERR(bd)) {
1029 printk(ASUS_ERR
1030 "Could not register asus backlight device\n");
1031 asus_backlight_device = NULL;
1032 return PTR_ERR(bd);
1033 }
1034
1035 asus_backlight_device = bd;
1036
1037 down(&bd->sem);
1038 if (likely(bd->props)) {
1039 bd->props->brightness = read_brightness(NULL);
1040 bd->props->power = FB_BLANK_UNBLANK;
1041 if (likely(bd->props->update_status))
1042 bd->props->update_status(bd);
1043 }
1044 up(&bd->sem);
1045 }
1046 return 0;
1047}
1048
1049static int asus_led_register(acpi_handle handle,
1050 struct led_classdev *ldev, struct device *dev)
1051{
1052 if (!handle)
1053 return 0;
1054
1055 return led_classdev_register(dev, ldev);
1056}
1057
1058#define ASUS_LED_REGISTER(object, device) \
1059 asus_led_register(object##_set_handle, &object##_led, device)
1060
1061static int asus_led_init(struct device *dev)
1062{
1063 int rv;
1064
1065 rv = ASUS_LED_REGISTER(mled, dev);
1066 if (rv)
1067 return rv;
1068
1069 rv = ASUS_LED_REGISTER(tled, dev);
1070 if (rv)
1071 return rv;
1072
1073 rv = ASUS_LED_REGISTER(rled, dev);
1074 if (rv)
1075 return rv;
1076
1077 rv = ASUS_LED_REGISTER(pled, dev);
1078 if (rv)
1079 return rv;
1080
1081 led_workqueue = create_singlethread_workqueue("led_workqueue");
1082 if (!led_workqueue)
1083 return -ENOMEM;
1084
1085 return 0;
1086}
1087
1088static int __init asus_laptop_init(void)
1089{
1090 struct device *dev;
1091 int result;
1092
1093 if (acpi_disabled)
1094 return -ENODEV;
1095
1096 if (!acpi_specific_hotkey_enabled) {
1097 printk(ASUS_ERR "Using generic hotkey driver\n");
1098 return -ENODEV;
1099 }
1100
1101 result = acpi_bus_register_driver(&asus_hotk_driver);
1102 if (result < 0)
1103 return result;
1104
1105 /*
1106 * This is a bit of a kludge. We only want this module loaded
1107 * for ASUS systems, but there's currently no way to probe the
1108 * ACPI namespace for ASUS HIDs. So we just return failure if
1109 * we didn't find one, which will cause the module to be
1110 * unloaded.
1111 */
1112 if (!asus_hotk_found) {
1113 acpi_bus_unregister_driver(&asus_hotk_driver);
1114 return -ENODEV;
1115 }
1116
1117 dev = acpi_get_physical_device(hotk->device->handle);
1118
1119 result = asus_backlight_init(dev);
1120 if (result)
1121 goto fail_backlight;
1122
1123 result = asus_led_init(dev);
1124 if (result)
1125 goto fail_led;
1126
1127 /* Register platform stuff */
1128 result = platform_driver_register(&asuspf_driver);
1129 if (result)
1130 goto fail_platform_driver;
1131
1132 asuspf_device = platform_device_alloc(ASUS_HOTK_FILE, -1);
1133 if (!asuspf_device) {
1134 result = -ENOMEM;
1135 goto fail_platform_device1;
1136 }
1137
1138 result = platform_device_add(asuspf_device);
1139 if (result)
1140 goto fail_platform_device2;
1141
1142 result = sysfs_create_group(&asuspf_device->dev.kobj,
1143 &asuspf_attribute_group);
1144 if (result)
1145 goto fail_sysfs;
1146
1147 return 0;
1148
1149 fail_sysfs:
1150 platform_device_del(asuspf_device);
1151
1152 fail_platform_device2:
1153 platform_device_put(asuspf_device);
1154
1155 fail_platform_device1:
1156 platform_driver_unregister(&asuspf_driver);
1157
1158 fail_platform_driver:
1159 asus_led_exit();
1160
1161 fail_led:
1162 asus_backlight_exit();
1163
1164 fail_backlight:
1165
1166 return result;
1167}
1168
1169module_init(asus_laptop_init);
1170module_exit(asus_laptop_exit);