diff options
Diffstat (limited to 'drivers/acpi/toshiba_acpi.c')
-rw-r--r-- | drivers/acpi/toshiba_acpi.c | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/drivers/acpi/toshiba_acpi.c b/drivers/acpi/toshiba_acpi.c new file mode 100644 index 000000000000..c84997c9f964 --- /dev/null +++ b/drivers/acpi/toshiba_acpi.c | |||
@@ -0,0 +1,575 @@ | |||
1 | /* | ||
2 | * toshiba_acpi.c - Toshiba Laptop ACPI Extras | ||
3 | * | ||
4 | * | ||
5 | * Copyright (C) 2002-2004 John Belmonte | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | * | ||
21 | * | ||
22 | * The devolpment page for this driver is located at | ||
23 | * http://memebeam.org/toys/ToshibaAcpiDriver. | ||
24 | * | ||
25 | * Credits: | ||
26 | * Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse | ||
27 | * engineering the Windows drivers | ||
28 | * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 | ||
29 | * Rob Miller - TV out and hotkeys help | ||
30 | * | ||
31 | * | ||
32 | * TODO | ||
33 | * | ||
34 | */ | ||
35 | |||
36 | #define TOSHIBA_ACPI_VERSION "0.18" | ||
37 | #define PROC_INTERFACE_VERSION 1 | ||
38 | |||
39 | #include <linux/kernel.h> | ||
40 | #include <linux/module.h> | ||
41 | #include <linux/init.h> | ||
42 | #include <linux/types.h> | ||
43 | #include <linux/proc_fs.h> | ||
44 | #include <asm/uaccess.h> | ||
45 | |||
46 | #include <acpi/acpi_drivers.h> | ||
47 | |||
48 | MODULE_AUTHOR("John Belmonte"); | ||
49 | MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); | ||
50 | MODULE_LICENSE("GPL"); | ||
51 | |||
52 | #define MY_LOGPREFIX "toshiba_acpi: " | ||
53 | #define MY_ERR KERN_ERR MY_LOGPREFIX | ||
54 | #define MY_NOTICE KERN_NOTICE MY_LOGPREFIX | ||
55 | #define MY_INFO KERN_INFO MY_LOGPREFIX | ||
56 | |||
57 | /* Toshiba ACPI method paths */ | ||
58 | #define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" | ||
59 | #define METHOD_HCI_1 "\\_SB_.VALD.GHCI" | ||
60 | #define METHOD_HCI_2 "\\_SB_.VALZ.GHCI" | ||
61 | #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" | ||
62 | |||
63 | /* Toshiba HCI interface definitions | ||
64 | * | ||
65 | * HCI is Toshiba's "Hardware Control Interface" which is supposed to | ||
66 | * be uniform across all their models. Ideally we would just call | ||
67 | * dedicated ACPI methods instead of using this primitive interface. | ||
68 | * However the ACPI methods seem to be incomplete in some areas (for | ||
69 | * example they allow setting, but not reading, the LCD brightness value), | ||
70 | * so this is still useful. | ||
71 | */ | ||
72 | |||
73 | #define HCI_WORDS 6 | ||
74 | |||
75 | /* operations */ | ||
76 | #define HCI_SET 0xff00 | ||
77 | #define HCI_GET 0xfe00 | ||
78 | |||
79 | /* return codes */ | ||
80 | #define HCI_SUCCESS 0x0000 | ||
81 | #define HCI_FAILURE 0x1000 | ||
82 | #define HCI_NOT_SUPPORTED 0x8000 | ||
83 | #define HCI_EMPTY 0x8c00 | ||
84 | |||
85 | /* registers */ | ||
86 | #define HCI_FAN 0x0004 | ||
87 | #define HCI_SYSTEM_EVENT 0x0016 | ||
88 | #define HCI_VIDEO_OUT 0x001c | ||
89 | #define HCI_HOTKEY_EVENT 0x001e | ||
90 | #define HCI_LCD_BRIGHTNESS 0x002a | ||
91 | |||
92 | /* field definitions */ | ||
93 | #define HCI_LCD_BRIGHTNESS_BITS 3 | ||
94 | #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) | ||
95 | #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) | ||
96 | #define HCI_VIDEO_OUT_LCD 0x1 | ||
97 | #define HCI_VIDEO_OUT_CRT 0x2 | ||
98 | #define HCI_VIDEO_OUT_TV 0x4 | ||
99 | |||
100 | /* utility | ||
101 | */ | ||
102 | |||
103 | static __inline__ void | ||
104 | _set_bit(u32* word, u32 mask, int value) | ||
105 | { | ||
106 | *word = (*word & ~mask) | (mask * value); | ||
107 | } | ||
108 | |||
109 | /* acpi interface wrappers | ||
110 | */ | ||
111 | |||
112 | static int | ||
113 | is_valid_acpi_path(const char* methodName) | ||
114 | { | ||
115 | acpi_handle handle; | ||
116 | acpi_status status; | ||
117 | |||
118 | status = acpi_get_handle(NULL, (char*)methodName, &handle); | ||
119 | return !ACPI_FAILURE(status); | ||
120 | } | ||
121 | |||
122 | static int | ||
123 | write_acpi_int(const char* methodName, int val) | ||
124 | { | ||
125 | struct acpi_object_list params; | ||
126 | union acpi_object in_objs[1]; | ||
127 | acpi_status status; | ||
128 | |||
129 | params.count = sizeof(in_objs)/sizeof(in_objs[0]); | ||
130 | params.pointer = in_objs; | ||
131 | in_objs[0].type = ACPI_TYPE_INTEGER; | ||
132 | in_objs[0].integer.value = val; | ||
133 | |||
134 | status = acpi_evaluate_object(NULL, (char*)methodName, ¶ms, NULL); | ||
135 | return (status == AE_OK); | ||
136 | } | ||
137 | |||
138 | #if 0 | ||
139 | static int | ||
140 | read_acpi_int(const char* methodName, int* pVal) | ||
141 | { | ||
142 | struct acpi_buffer results; | ||
143 | union acpi_object out_objs[1]; | ||
144 | acpi_status status; | ||
145 | |||
146 | results.length = sizeof(out_objs); | ||
147 | results.pointer = out_objs; | ||
148 | |||
149 | status = acpi_evaluate_object(0, (char*)methodName, 0, &results); | ||
150 | *pVal = out_objs[0].integer.value; | ||
151 | |||
152 | return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); | ||
153 | } | ||
154 | #endif | ||
155 | |||
156 | static const char* method_hci /*= 0*/; | ||
157 | |||
158 | /* Perform a raw HCI call. Here we don't care about input or output buffer | ||
159 | * format. | ||
160 | */ | ||
161 | static acpi_status | ||
162 | hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) | ||
163 | { | ||
164 | struct acpi_object_list params; | ||
165 | union acpi_object in_objs[HCI_WORDS]; | ||
166 | struct acpi_buffer results; | ||
167 | union acpi_object out_objs[HCI_WORDS+1]; | ||
168 | acpi_status status; | ||
169 | int i; | ||
170 | |||
171 | params.count = HCI_WORDS; | ||
172 | params.pointer = in_objs; | ||
173 | for (i = 0; i < HCI_WORDS; ++i) { | ||
174 | in_objs[i].type = ACPI_TYPE_INTEGER; | ||
175 | in_objs[i].integer.value = in[i]; | ||
176 | } | ||
177 | |||
178 | results.length = sizeof(out_objs); | ||
179 | results.pointer = out_objs; | ||
180 | |||
181 | status = acpi_evaluate_object(NULL, (char*)method_hci, ¶ms, | ||
182 | &results); | ||
183 | if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { | ||
184 | for (i = 0; i < out_objs->package.count; ++i) { | ||
185 | out[i] = out_objs->package.elements[i].integer.value; | ||
186 | } | ||
187 | } | ||
188 | |||
189 | return status; | ||
190 | } | ||
191 | |||
192 | /* common hci tasks (get or set one value) | ||
193 | * | ||
194 | * In addition to the ACPI status, the HCI system returns a result which | ||
195 | * may be useful (such as "not supported"). | ||
196 | */ | ||
197 | |||
198 | static acpi_status | ||
199 | hci_write1(u32 reg, u32 in1, u32* result) | ||
200 | { | ||
201 | u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; | ||
202 | u32 out[HCI_WORDS]; | ||
203 | acpi_status status = hci_raw(in, out); | ||
204 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | ||
205 | return status; | ||
206 | } | ||
207 | |||
208 | static acpi_status | ||
209 | hci_read1(u32 reg, u32* out1, u32* result) | ||
210 | { | ||
211 | u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; | ||
212 | u32 out[HCI_WORDS]; | ||
213 | acpi_status status = hci_raw(in, out); | ||
214 | *out1 = out[2]; | ||
215 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | ||
216 | return status; | ||
217 | } | ||
218 | |||
219 | static struct proc_dir_entry* toshiba_proc_dir /*= 0*/; | ||
220 | static int force_fan; | ||
221 | static int last_key_event; | ||
222 | static int key_event_valid; | ||
223 | |||
224 | typedef struct _ProcItem | ||
225 | { | ||
226 | const char* name; | ||
227 | char* (*read_func)(char*); | ||
228 | unsigned long (*write_func)(const char*, unsigned long); | ||
229 | } ProcItem; | ||
230 | |||
231 | /* proc file handlers | ||
232 | */ | ||
233 | |||
234 | static int | ||
235 | dispatch_read(char* page, char** start, off_t off, int count, int* eof, | ||
236 | ProcItem* item) | ||
237 | { | ||
238 | char* p = page; | ||
239 | int len; | ||
240 | |||
241 | if (off == 0) | ||
242 | p = item->read_func(p); | ||
243 | |||
244 | /* ISSUE: I don't understand this code */ | ||
245 | len = (p - page); | ||
246 | if (len <= off+count) *eof = 1; | ||
247 | *start = page + off; | ||
248 | len -= off; | ||
249 | if (len>count) len = count; | ||
250 | if (len<0) len = 0; | ||
251 | return len; | ||
252 | } | ||
253 | |||
254 | static int | ||
255 | dispatch_write(struct file* file, const char __user * buffer, | ||
256 | unsigned long count, ProcItem* item) | ||
257 | { | ||
258 | int result; | ||
259 | char* tmp_buffer; | ||
260 | |||
261 | /* Arg buffer points to userspace memory, which can't be accessed | ||
262 | * directly. Since we're making a copy, zero-terminate the | ||
263 | * destination so that sscanf can be used on it safely. | ||
264 | */ | ||
265 | tmp_buffer = kmalloc(count + 1, GFP_KERNEL); | ||
266 | if (copy_from_user(tmp_buffer, buffer, count)) { | ||
267 | result = -EFAULT; | ||
268 | } | ||
269 | else { | ||
270 | tmp_buffer[count] = 0; | ||
271 | result = item->write_func(tmp_buffer, count); | ||
272 | } | ||
273 | kfree(tmp_buffer); | ||
274 | return result; | ||
275 | } | ||
276 | |||
277 | static char* | ||
278 | read_lcd(char* p) | ||
279 | { | ||
280 | u32 hci_result; | ||
281 | u32 value; | ||
282 | |||
283 | hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); | ||
284 | if (hci_result == HCI_SUCCESS) { | ||
285 | value = value >> HCI_LCD_BRIGHTNESS_SHIFT; | ||
286 | p += sprintf(p, "brightness: %d\n", value); | ||
287 | p += sprintf(p, "brightness_levels: %d\n", | ||
288 | HCI_LCD_BRIGHTNESS_LEVELS); | ||
289 | } else { | ||
290 | printk(MY_ERR "Error reading LCD brightness\n"); | ||
291 | } | ||
292 | |||
293 | return p; | ||
294 | } | ||
295 | |||
296 | static unsigned long | ||
297 | write_lcd(const char* buffer, unsigned long count) | ||
298 | { | ||
299 | int value; | ||
300 | u32 hci_result; | ||
301 | |||
302 | if (sscanf(buffer, " brightness : %i", &value) == 1 && | ||
303 | value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { | ||
304 | value = value << HCI_LCD_BRIGHTNESS_SHIFT; | ||
305 | hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); | ||
306 | if (hci_result != HCI_SUCCESS) | ||
307 | return -EFAULT; | ||
308 | } else { | ||
309 | return -EINVAL; | ||
310 | } | ||
311 | |||
312 | return count; | ||
313 | } | ||
314 | |||
315 | static char* | ||
316 | read_video(char* p) | ||
317 | { | ||
318 | u32 hci_result; | ||
319 | u32 value; | ||
320 | |||
321 | hci_read1(HCI_VIDEO_OUT, &value, &hci_result); | ||
322 | if (hci_result == HCI_SUCCESS) { | ||
323 | int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; | ||
324 | int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; | ||
325 | int is_tv = (value & HCI_VIDEO_OUT_TV ) ? 1 : 0; | ||
326 | p += sprintf(p, "lcd_out: %d\n", is_lcd); | ||
327 | p += sprintf(p, "crt_out: %d\n", is_crt); | ||
328 | p += sprintf(p, "tv_out: %d\n", is_tv); | ||
329 | } else { | ||
330 | printk(MY_ERR "Error reading video out status\n"); | ||
331 | } | ||
332 | |||
333 | return p; | ||
334 | } | ||
335 | |||
336 | static unsigned long | ||
337 | write_video(const char* buffer, unsigned long count) | ||
338 | { | ||
339 | int value; | ||
340 | int remain = count; | ||
341 | int lcd_out = -1; | ||
342 | int crt_out = -1; | ||
343 | int tv_out = -1; | ||
344 | u32 hci_result; | ||
345 | int video_out; | ||
346 | |||
347 | /* scan expression. Multiple expressions may be delimited with ; | ||
348 | * | ||
349 | * NOTE: to keep scanning simple, invalid fields are ignored | ||
350 | */ | ||
351 | while (remain) { | ||
352 | if (sscanf(buffer, " lcd_out : %i", &value) == 1) | ||
353 | lcd_out = value & 1; | ||
354 | else if (sscanf(buffer, " crt_out : %i", &value) == 1) | ||
355 | crt_out = value & 1; | ||
356 | else if (sscanf(buffer, " tv_out : %i", &value) == 1) | ||
357 | tv_out = value & 1; | ||
358 | /* advance to one character past the next ; */ | ||
359 | do { | ||
360 | ++buffer; | ||
361 | --remain; | ||
362 | } | ||
363 | while (remain && *(buffer-1) != ';'); | ||
364 | } | ||
365 | |||
366 | hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); | ||
367 | if (hci_result == HCI_SUCCESS) { | ||
368 | int new_video_out = video_out; | ||
369 | if (lcd_out != -1) | ||
370 | _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); | ||
371 | if (crt_out != -1) | ||
372 | _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); | ||
373 | if (tv_out != -1) | ||
374 | _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); | ||
375 | /* To avoid unnecessary video disruption, only write the new | ||
376 | * video setting if something changed. */ | ||
377 | if (new_video_out != video_out) | ||
378 | write_acpi_int(METHOD_VIDEO_OUT, new_video_out); | ||
379 | } else { | ||
380 | return -EFAULT; | ||
381 | } | ||
382 | |||
383 | return count; | ||
384 | } | ||
385 | |||
386 | static char* | ||
387 | read_fan(char* p) | ||
388 | { | ||
389 | u32 hci_result; | ||
390 | u32 value; | ||
391 | |||
392 | hci_read1(HCI_FAN, &value, &hci_result); | ||
393 | if (hci_result == HCI_SUCCESS) { | ||
394 | p += sprintf(p, "running: %d\n", (value > 0)); | ||
395 | p += sprintf(p, "force_on: %d\n", force_fan); | ||
396 | } else { | ||
397 | printk(MY_ERR "Error reading fan status\n"); | ||
398 | } | ||
399 | |||
400 | return p; | ||
401 | } | ||
402 | |||
403 | static unsigned long | ||
404 | write_fan(const char* buffer, unsigned long count) | ||
405 | { | ||
406 | int value; | ||
407 | u32 hci_result; | ||
408 | |||
409 | if (sscanf(buffer, " force_on : %i", &value) == 1 && | ||
410 | value >= 0 && value <= 1) { | ||
411 | hci_write1(HCI_FAN, value, &hci_result); | ||
412 | if (hci_result != HCI_SUCCESS) | ||
413 | return -EFAULT; | ||
414 | else | ||
415 | force_fan = value; | ||
416 | } else { | ||
417 | return -EINVAL; | ||
418 | } | ||
419 | |||
420 | return count; | ||
421 | } | ||
422 | |||
423 | static char* | ||
424 | read_keys(char* p) | ||
425 | { | ||
426 | u32 hci_result; | ||
427 | u32 value; | ||
428 | |||
429 | if (!key_event_valid) { | ||
430 | hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); | ||
431 | if (hci_result == HCI_SUCCESS) { | ||
432 | key_event_valid = 1; | ||
433 | last_key_event = value; | ||
434 | } else if (hci_result == HCI_EMPTY) { | ||
435 | /* better luck next time */ | ||
436 | } else if (hci_result == HCI_NOT_SUPPORTED) { | ||
437 | /* This is a workaround for an unresolved issue on | ||
438 | * some machines where system events sporadically | ||
439 | * become disabled. */ | ||
440 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | ||
441 | printk(MY_NOTICE "Re-enabled hotkeys\n"); | ||
442 | } else { | ||
443 | printk(MY_ERR "Error reading hotkey status\n"); | ||
444 | goto end; | ||
445 | } | ||
446 | } | ||
447 | |||
448 | p += sprintf(p, "hotkey_ready: %d\n", key_event_valid); | ||
449 | p += sprintf(p, "hotkey: 0x%04x\n", last_key_event); | ||
450 | |||
451 | end: | ||
452 | return p; | ||
453 | } | ||
454 | |||
455 | static unsigned long | ||
456 | write_keys(const char* buffer, unsigned long count) | ||
457 | { | ||
458 | int value; | ||
459 | |||
460 | if (sscanf(buffer, " hotkey_ready : %i", &value) == 1 && | ||
461 | value == 0) { | ||
462 | key_event_valid = 0; | ||
463 | } else { | ||
464 | return -EINVAL; | ||
465 | } | ||
466 | |||
467 | return count; | ||
468 | } | ||
469 | |||
470 | static char* | ||
471 | read_version(char* p) | ||
472 | { | ||
473 | p += sprintf(p, "driver: %s\n", TOSHIBA_ACPI_VERSION); | ||
474 | p += sprintf(p, "proc_interface: %d\n", | ||
475 | PROC_INTERFACE_VERSION); | ||
476 | return p; | ||
477 | } | ||
478 | |||
479 | /* proc and module init | ||
480 | */ | ||
481 | |||
482 | #define PROC_TOSHIBA "toshiba" | ||
483 | |||
484 | static ProcItem proc_items[] = | ||
485 | { | ||
486 | { "lcd" , read_lcd , write_lcd }, | ||
487 | { "video" , read_video , write_video }, | ||
488 | { "fan" , read_fan , write_fan }, | ||
489 | { "keys" , read_keys , write_keys }, | ||
490 | { "version" , read_version , NULL }, | ||
491 | { NULL } | ||
492 | }; | ||
493 | |||
494 | static acpi_status __init | ||
495 | add_device(void) | ||
496 | { | ||
497 | struct proc_dir_entry* proc; | ||
498 | ProcItem* item; | ||
499 | |||
500 | for (item = proc_items; item->name; ++item) | ||
501 | { | ||
502 | proc = create_proc_read_entry(item->name, | ||
503 | S_IFREG | S_IRUGO | S_IWUSR, | ||
504 | toshiba_proc_dir, (read_proc_t*)dispatch_read, item); | ||
505 | if (proc) | ||
506 | proc->owner = THIS_MODULE; | ||
507 | if (proc && item->write_func) | ||
508 | proc->write_proc = (write_proc_t*)dispatch_write; | ||
509 | } | ||
510 | |||
511 | return AE_OK; | ||
512 | } | ||
513 | |||
514 | static acpi_status __exit | ||
515 | remove_device(void) | ||
516 | { | ||
517 | ProcItem* item; | ||
518 | |||
519 | for (item = proc_items; item->name; ++item) | ||
520 | remove_proc_entry(item->name, toshiba_proc_dir); | ||
521 | return AE_OK; | ||
522 | } | ||
523 | |||
524 | static int __init | ||
525 | toshiba_acpi_init(void) | ||
526 | { | ||
527 | acpi_status status = AE_OK; | ||
528 | u32 hci_result; | ||
529 | |||
530 | if (acpi_disabled) | ||
531 | return -ENODEV; | ||
532 | /* simple device detection: look for HCI method */ | ||
533 | if (is_valid_acpi_path(METHOD_HCI_1)) | ||
534 | method_hci = METHOD_HCI_1; | ||
535 | else if (is_valid_acpi_path(METHOD_HCI_2)) | ||
536 | method_hci = METHOD_HCI_2; | ||
537 | else | ||
538 | return -ENODEV; | ||
539 | |||
540 | printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", | ||
541 | TOSHIBA_ACPI_VERSION); | ||
542 | printk(MY_INFO " HCI method: %s\n", method_hci); | ||
543 | |||
544 | force_fan = 0; | ||
545 | key_event_valid = 0; | ||
546 | |||
547 | /* enable event fifo */ | ||
548 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | ||
549 | |||
550 | toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); | ||
551 | if (!toshiba_proc_dir) { | ||
552 | status = AE_ERROR; | ||
553 | } else { | ||
554 | toshiba_proc_dir->owner = THIS_MODULE; | ||
555 | status = add_device(); | ||
556 | if (ACPI_FAILURE(status)) | ||
557 | remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); | ||
558 | } | ||
559 | |||
560 | return (ACPI_SUCCESS(status)) ? 0 : -ENODEV; | ||
561 | } | ||
562 | |||
563 | static void __exit | ||
564 | toshiba_acpi_exit(void) | ||
565 | { | ||
566 | remove_device(); | ||
567 | |||
568 | if (toshiba_proc_dir) | ||
569 | remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); | ||
570 | |||
571 | return; | ||
572 | } | ||
573 | |||
574 | module_init(toshiba_acpi_init); | ||
575 | module_exit(toshiba_acpi_exit); | ||