]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/misc/eeepc-laptop.c
eeepc-laptop: add base driver
[linux-2.6-omap-h63xx.git] / drivers / misc / eeepc-laptop.c
1 /*
2  *  eepc-laptop.c - Asus Eee PC extras
3  *
4  *  Based on asus_acpi.c as patched for the Eee PC by Asus:
5  *  ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar
6  *  Based on eee.c from eeepc-linux
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
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/init.h>
22 #include <linux/types.h>
23 #include <linux/platform_device.h>
24 #include <acpi/acpi_drivers.h>
25 #include <acpi/acpi_bus.h>
26 #include <linux/uaccess.h>
27
28 #define EEEPC_LAPTOP_VERSION    "0.1"
29
30 #define EEEPC_HOTK_NAME         "Eee PC Hotkey Driver"
31 #define EEEPC_HOTK_FILE         "eeepc"
32 #define EEEPC_HOTK_CLASS        "hotkey"
33 #define EEEPC_HOTK_DEVICE_NAME  "Hotkey"
34 #define EEEPC_HOTK_HID          "ASUS010"
35
36 #define EEEPC_LOG       EEEPC_HOTK_FILE ": "
37 #define EEEPC_ERR       KERN_ERR        EEEPC_LOG
38 #define EEEPC_WARNING   KERN_WARNING    EEEPC_LOG
39 #define EEEPC_NOTICE    KERN_NOTICE     EEEPC_LOG
40 #define EEEPC_INFO      KERN_INFO       EEEPC_LOG
41
42 /*
43  * Definitions for Asus EeePC
44  */
45 #define NOTIFY_WLAN_ON  0x10
46
47 enum {
48         DISABLE_ASL_WLAN = 0x0001,
49         DISABLE_ASL_BLUETOOTH = 0x0002,
50         DISABLE_ASL_IRDA = 0x0004,
51         DISABLE_ASL_CAMERA = 0x0008,
52         DISABLE_ASL_TV = 0x0010,
53         DISABLE_ASL_GPS = 0x0020,
54         DISABLE_ASL_DISPLAYSWITCH = 0x0040,
55         DISABLE_ASL_MODEM = 0x0080,
56         DISABLE_ASL_CARDREADER = 0x0100
57 };
58
59 enum {
60         CM_ASL_WLAN = 0,
61         CM_ASL_BLUETOOTH,
62         CM_ASL_IRDA,
63         CM_ASL_1394,
64         CM_ASL_CAMERA,
65         CM_ASL_TV,
66         CM_ASL_GPS,
67         CM_ASL_DVDROM,
68         CM_ASL_DISPLAYSWITCH,
69         CM_ASL_PANELBRIGHT,
70         CM_ASL_BIOSFLASH,
71         CM_ASL_ACPIFLASH,
72         CM_ASL_CPUFV,
73         CM_ASL_CPUTEMPERATURE,
74         CM_ASL_FANCPU,
75         CM_ASL_FANCHASSIS,
76         CM_ASL_USBPORT1,
77         CM_ASL_USBPORT2,
78         CM_ASL_USBPORT3,
79         CM_ASL_MODEM,
80         CM_ASL_CARDREADER,
81         CM_ASL_LID
82 };
83
84 const char *cm_getv[] = {
85         "WLDG", NULL, NULL, NULL,
86         "CAMG", NULL, NULL, NULL,
87         NULL, "PBLG", NULL, NULL,
88         "CFVG", NULL, NULL, NULL,
89         "USBG", NULL, NULL, "MODG",
90         "CRDG", "LIDG"
91 };
92
93 const char *cm_setv[] = {
94         "WLDS", NULL, NULL, NULL,
95         "CAMS", NULL, NULL, NULL,
96         "SDSP", "PBLS", "HDPS", NULL,
97         "CFVS", NULL, NULL, NULL,
98         "USBG", NULL, NULL, "MODS",
99         "CRDS", NULL
100 };
101
102 /*
103  * This is the main structure, we can use it to store useful information
104  * about the hotk device
105  */
106 struct eeepc_hotk {
107         struct acpi_device *device;     /* the device we are in */
108         acpi_handle handle;             /* the handle of the hotk device */
109         u32 cm_supported;               /* the control methods supported
110                                            by this BIOS */
111         uint init_flag;                 /* Init flags */
112         u16 event_count[128];           /* count for each event */
113 };
114
115 /* The actual device the driver binds to */
116 static struct eeepc_hotk *ehotk;
117
118 /* Platform device/driver */
119 static struct platform_driver platform_driver = {
120         .driver = {
121                 .name = EEEPC_HOTK_FILE,
122                 .owner = THIS_MODULE,
123         }
124 };
125
126 static struct platform_device *platform_device;
127
128 /*
129  * The hotkey driver declaration
130  */
131 static int eeepc_hotk_add(struct acpi_device *device);
132 static int eeepc_hotk_remove(struct acpi_device *device, int type);
133
134 static const struct acpi_device_id eeepc_device_ids[] = {
135         {EEEPC_HOTK_HID, 0},
136         {"", 0},
137 };
138 MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
139
140 static struct acpi_driver eeepc_hotk_driver = {
141         .name = EEEPC_HOTK_NAME,
142         .class = EEEPC_HOTK_CLASS,
143         .ids = eeepc_device_ids,
144         .ops = {
145                 .add = eeepc_hotk_add,
146                 .remove = eeepc_hotk_remove,
147         },
148 };
149
150 MODULE_AUTHOR("Corentin Chary, Eric Cooper");
151 MODULE_DESCRIPTION(EEEPC_HOTK_NAME);
152 MODULE_LICENSE("GPL");
153
154 /*
155  * ACPI Helpers
156  */
157 static int write_acpi_int(acpi_handle handle, const char *method, int val,
158                           struct acpi_buffer *output)
159 {
160         struct acpi_object_list params;
161         union acpi_object in_obj;
162         acpi_status status;
163
164         params.count = 1;
165         params.pointer = &in_obj;
166         in_obj.type = ACPI_TYPE_INTEGER;
167         in_obj.integer.value = val;
168
169         status = acpi_evaluate_object(handle, (char *)method, &params, output);
170         return (status == AE_OK ? 0 : -1);
171 }
172
173 static int read_acpi_int(acpi_handle handle, const char *method, int *val)
174 {
175         acpi_status status;
176         ulong result;
177
178         status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
179         if (ACPI_FAILURE(status)) {
180                 *val = -1;
181                 return -1;
182         } else {
183                 *val = result;
184                 return 0;
185         }
186 }
187
188 static int set_acpi(int cm, int value)
189 {
190         if (ehotk->cm_supported & (0x1 << cm)) {
191                 const char *method = cm_setv[cm];
192                 if (method == NULL)
193                         return -ENODEV;
194                 if (write_acpi_int(ehotk->handle, method, value, NULL))
195                         printk(EEEPC_WARNING "Error writing %s\n", method);
196         }
197         return 0;
198 }
199
200 static int get_acpi(int cm)
201 {
202         int value = -1;
203         if ((ehotk->cm_supported & (0x1 << cm))) {
204                 const char *method = cm_getv[cm];
205                 if (method == NULL)
206                         return -ENODEV;
207                 if (read_acpi_int(ehotk->handle, method, &value))
208                         printk(EEEPC_WARNING "Error reading %s\n", method);
209         }
210         return value;
211 }
212
213 /*
214  * Sys helpers
215  */
216 static int parse_arg(const char *buf, unsigned long count, int *val)
217 {
218         if (!count)
219                 return 0;
220         if (sscanf(buf, "%i", val) != 1)
221                 return -EINVAL;
222         return count;
223 }
224
225 static ssize_t store_sys_acpi(int cm, const char *buf, size_t count)
226 {
227         int rv, value;
228
229         rv = parse_arg(buf, count, &value);
230         if (rv > 0)
231                 set_acpi(cm, value);
232         return rv;
233 }
234
235 static ssize_t show_sys_acpi(int cm, char *buf)
236 {
237         return sprintf(buf, "%d\n", get_acpi(cm));
238 }
239
240 #define EEEPC_CREATE_DEVICE_ATTR(_name, _cm)                            \
241         static ssize_t show_##_name(struct device *dev,                 \
242                                     struct device_attribute *attr,      \
243                                     char *buf)                          \
244         {                                                               \
245                 return show_sys_acpi(_cm, buf);                         \
246         }                                                               \
247         static ssize_t store_##_name(struct device *dev,                \
248                                      struct device_attribute *attr,     \
249                                      const char *buf, size_t count)     \
250         {                                                               \
251                 return store_sys_acpi(_cm, buf, count);                 \
252         }                                                               \
253         static struct device_attribute dev_attr_##_name = {             \
254                 .attr = {                                               \
255                         .name = __stringify(_name),                     \
256                         .mode = 0644 },                                 \
257                 .show   = show_##_name,                                 \
258                 .store  = store_##_name,                                \
259         }
260
261 EEEPC_CREATE_DEVICE_ATTR(camera, CM_ASL_CAMERA);
262 EEEPC_CREATE_DEVICE_ATTR(cardr, CM_ASL_CARDREADER);
263 EEEPC_CREATE_DEVICE_ATTR(disp, CM_ASL_DISPLAYSWITCH);
264 EEEPC_CREATE_DEVICE_ATTR(wlan, CM_ASL_WLAN);
265
266 static struct attribute *platform_attributes[] = {
267         &dev_attr_camera.attr,
268         &dev_attr_cardr.attr,
269         &dev_attr_disp.attr,
270         &dev_attr_wlan.attr,
271         NULL
272 };
273
274 static struct attribute_group platform_attribute_group = {
275         .attrs = platform_attributes
276 };
277
278 /*
279  * Hotkey functions
280  */
281 static int eeepc_hotk_check(void)
282 {
283         struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
284         int result;
285
286         result = acpi_bus_get_status(ehotk->device);
287         if (result)
288                 return result;
289         if (ehotk->device->status.present) {
290                 if (write_acpi_int(ehotk->handle, "INIT", ehotk->init_flag,
291                                     &buffer)) {
292                         printk(EEEPC_ERR "Hotkey initialization failed\n");
293                         return -ENODEV;
294                 } else {
295                         printk(EEEPC_NOTICE "Hotkey init flags 0x%x\n",
296                                ehotk->init_flag);
297                 }
298                 /* get control methods supported */
299                 if (read_acpi_int(ehotk->handle, "CMSG"
300                                    , &ehotk->cm_supported)) {
301                         printk(EEEPC_ERR
302                                "Get control methods supported failed\n");
303                         return -ENODEV;
304                 } else {
305                         printk(EEEPC_INFO
306                                "Get control methods supported: 0x%x\n",
307                                ehotk->cm_supported);
308                 }
309         } else {
310                 printk(EEEPC_ERR "Hotkey device not present, aborting\n");
311                 return -EINVAL;
312         }
313         return 0;
314 }
315
316 static void notify_wlan(u32 *event)
317 {
318         /* if DISABLE_ASL_WLAN is set, the notify code for fn+f2
319            will always be 0x10 */
320         if (ehotk->cm_supported & (0x1 << CM_ASL_WLAN)) {
321                 const char *method = cm_getv[CM_ASL_WLAN];
322                 int value;
323                 if (read_acpi_int(ehotk->handle, method, &value))
324                         printk(EEEPC_WARNING "Error reading %s\n",
325                                method);
326                 else if (value == 1)
327                         *event = 0x11;
328         }
329 }
330
331 static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data)
332 {
333         if (!ehotk)
334                 return;
335         if (event == NOTIFY_WLAN_ON && (DISABLE_ASL_WLAN & ehotk->init_flag))
336                 notify_wlan(&event);
337         acpi_bus_generate_proc_event(ehotk->device, event,
338                                      ehotk->event_count[event % 128]++);
339 }
340
341 static int eeepc_hotk_add(struct acpi_device *device)
342 {
343         acpi_status status = AE_OK;
344         int result;
345
346         if (!device)
347                  return -EINVAL;
348         printk(EEEPC_NOTICE EEEPC_HOTK_NAME "\n");
349         ehotk = kzalloc(sizeof(struct eeepc_hotk), GFP_KERNEL);
350         if (!ehotk)
351                 return -ENOMEM;
352         ehotk->init_flag = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
353         ehotk->handle = device->handle;
354         strcpy(acpi_device_name(device), EEEPC_HOTK_DEVICE_NAME);
355         strcpy(acpi_device_class(device), EEEPC_HOTK_CLASS);
356         acpi_driver_data(device) = ehotk;
357         ehotk->device = device;
358         result = eeepc_hotk_check();
359         if (result)
360                 goto end;
361         status = acpi_install_notify_handler(ehotk->handle, ACPI_SYSTEM_NOTIFY,
362                                              eeepc_hotk_notify, ehotk);
363         if (ACPI_FAILURE(status))
364                 printk(EEEPC_ERR "Error installing notify handler\n");
365  end:
366         if (result) {
367                 kfree(ehotk);
368                 ehotk = NULL;
369         }
370         return result;
371 }
372
373 static int eeepc_hotk_remove(struct acpi_device *device, int type)
374 {
375         acpi_status status = 0;
376
377         if (!device || !acpi_driver_data(device))
378                  return -EINVAL;
379         status = acpi_remove_notify_handler(ehotk->handle, ACPI_SYSTEM_NOTIFY,
380                                             eeepc_hotk_notify);
381         if (ACPI_FAILURE(status))
382                 printk(EEEPC_ERR "Error removing notify handler\n");
383         kfree(ehotk);
384         return 0;
385 }
386
387 /*
388  * exit/init
389  */
390 static void __exit eeepc_laptop_exit(void)
391 {
392         acpi_bus_unregister_driver(&eeepc_hotk_driver);
393         sysfs_remove_group(&platform_device->dev.kobj,
394                            &platform_attribute_group);
395         platform_device_unregister(platform_device);
396         platform_driver_unregister(&platform_driver);
397 }
398
399 static int __init eeepc_laptop_init(void)
400 {
401         struct device *dev;
402         int result;
403
404         if (acpi_disabled)
405                 return -ENODEV;
406         result = acpi_bus_register_driver(&eeepc_hotk_driver);
407         if (result < 0)
408                 return result;
409         if (!ehotk) {
410                 acpi_bus_unregister_driver(&eeepc_hotk_driver);
411                 return -ENODEV;
412         }
413         dev = acpi_get_physical_device(ehotk->device->handle);
414         /* Register platform stuff */
415         result = platform_driver_register(&platform_driver);
416         if (result)
417                 goto fail_platform_driver;
418         platform_device = platform_device_alloc(EEEPC_HOTK_FILE, -1);
419         if (!platform_device) {
420                 result = -ENOMEM;
421                 goto fail_platform_device1;
422         }
423         result = platform_device_add(platform_device);
424         if (result)
425                 goto fail_platform_device2;
426         result = sysfs_create_group(&platform_device->dev.kobj,
427                                     &platform_attribute_group);
428         if (result)
429                 goto fail_sysfs;
430         return 0;
431 fail_sysfs:
432         platform_device_del(platform_device);
433 fail_platform_device2:
434         platform_device_put(platform_device);
435 fail_platform_device1:
436         platform_driver_unregister(&platform_driver);
437 fail_platform_driver:
438         return result;
439 }
440
441 module_init(eeepc_laptop_init);
442 module_exit(eeepc_laptop_exit);