diff options
author | Eric Cooper <ecc@cmu.edu> | 2008-03-13 07:55:46 -0400 |
---|---|---|
committer | Len Brown <len.brown@intel.com> | 2008-04-29 10:08:06 -0400 |
commit | e59f87966adef2cb03d419530e3ade5159487d6d (patch) | |
tree | 9cdd798a987f5b71cf2281d6dfd408f15c33bf04 /drivers/misc/eeepc-laptop.c | |
parent | a01e035ebb552223c03f2d9138ffc73f2d4d3965 (diff) |
eeepc-laptop: add base driver
This patch is based on Eric Cooper's work to clean the original asus_acpi
given by Asus. It's a platform driver (/sys/devices/platform/eeepc/)
wich support:
- hotkeys - wlan on/off - camera on/off - cardr on/off
Signed-off-by: Corentin Chary <corentincj@iksaif.net>
Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'drivers/misc/eeepc-laptop.c')
-rw-r--r-- | drivers/misc/eeepc-laptop.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/drivers/misc/eeepc-laptop.c b/drivers/misc/eeepc-laptop.c new file mode 100644 index 000000000000..e34ff97530cd --- /dev/null +++ b/drivers/misc/eeepc-laptop.c | |||
@@ -0,0 +1,442 @@ | |||
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, ¶ms, 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); | ||