Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * toshiba_acpi.c - Toshiba Laptop ACPI Extras | |
3 | * | |
4 | * | |
5 | * Copyright (C) 2002-2004 John Belmonte | |
c41a40c5 | 6 | * Copyright (C) 2008 Philip Langdale |
1da177e4 LT |
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 devolpment page for this driver is located at | |
24 | * http://memebeam.org/toys/ToshibaAcpiDriver. | |
25 | * | |
26 | * Credits: | |
27 | * Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse | |
28 | * engineering the Windows drivers | |
29 | * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 | |
30 | * Rob Miller - TV out and hotkeys help | |
31 | * | |
32 | * | |
33 | * TODO | |
34 | * | |
35 | */ | |
36 | ||
c41a40c5 | 37 | #define TOSHIBA_ACPI_VERSION "0.19" |
1da177e4 LT |
38 | #define PROC_INTERFACE_VERSION 1 |
39 | ||
40 | #include <linux/kernel.h> | |
41 | #include <linux/module.h> | |
42 | #include <linux/init.h> | |
43 | #include <linux/types.h> | |
44 | #include <linux/proc_fs.h> | |
c9263557 | 45 | #include <linux/backlight.h> |
c41a40c5 | 46 | #include <linux/platform_device.h> |
47 | #include <linux/rfkill.h> | |
c9263557 | 48 | |
1da177e4 LT |
49 | #include <asm/uaccess.h> |
50 | ||
51 | #include <acpi/acpi_drivers.h> | |
52 | ||
53 | MODULE_AUTHOR("John Belmonte"); | |
54 | MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); | |
55 | MODULE_LICENSE("GPL"); | |
56 | ||
57 | #define MY_LOGPREFIX "toshiba_acpi: " | |
58 | #define MY_ERR KERN_ERR MY_LOGPREFIX | |
59 | #define MY_NOTICE KERN_NOTICE MY_LOGPREFIX | |
60 | #define MY_INFO KERN_INFO MY_LOGPREFIX | |
61 | ||
62 | /* Toshiba ACPI method paths */ | |
63 | #define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" | |
64 | #define METHOD_HCI_1 "\\_SB_.VALD.GHCI" | |
65 | #define METHOD_HCI_2 "\\_SB_.VALZ.GHCI" | |
66 | #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" | |
67 | ||
68 | /* Toshiba HCI interface definitions | |
69 | * | |
70 | * HCI is Toshiba's "Hardware Control Interface" which is supposed to | |
71 | * be uniform across all their models. Ideally we would just call | |
72 | * dedicated ACPI methods instead of using this primitive interface. | |
73 | * However the ACPI methods seem to be incomplete in some areas (for | |
74 | * example they allow setting, but not reading, the LCD brightness value), | |
75 | * so this is still useful. | |
76 | */ | |
77 | ||
78 | #define HCI_WORDS 6 | |
79 | ||
80 | /* operations */ | |
81 | #define HCI_SET 0xff00 | |
82 | #define HCI_GET 0xfe00 | |
83 | ||
84 | /* return codes */ | |
85 | #define HCI_SUCCESS 0x0000 | |
86 | #define HCI_FAILURE 0x1000 | |
87 | #define HCI_NOT_SUPPORTED 0x8000 | |
88 | #define HCI_EMPTY 0x8c00 | |
89 | ||
90 | /* registers */ | |
91 | #define HCI_FAN 0x0004 | |
92 | #define HCI_SYSTEM_EVENT 0x0016 | |
93 | #define HCI_VIDEO_OUT 0x001c | |
94 | #define HCI_HOTKEY_EVENT 0x001e | |
95 | #define HCI_LCD_BRIGHTNESS 0x002a | |
c41a40c5 | 96 | #define HCI_WIRELESS 0x0056 |
1da177e4 LT |
97 | |
98 | /* field definitions */ | |
99 | #define HCI_LCD_BRIGHTNESS_BITS 3 | |
100 | #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) | |
101 | #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) | |
102 | #define HCI_VIDEO_OUT_LCD 0x1 | |
103 | #define HCI_VIDEO_OUT_CRT 0x2 | |
104 | #define HCI_VIDEO_OUT_TV 0x4 | |
c41a40c5 | 105 | #define HCI_WIRELESS_KILL_SWITCH 0x01 |
106 | #define HCI_WIRELESS_BT_PRESENT 0x0f | |
107 | #define HCI_WIRELESS_BT_ATTACH 0x40 | |
108 | #define HCI_WIRELESS_BT_POWER 0x80 | |
1da177e4 | 109 | |
4db42c51 | 110 | static const struct acpi_device_id toshiba_device_ids[] = { |
111 | {"TOS6200", 0}, | |
c41a40c5 | 112 | {"TOS6208", 0}, |
4db42c51 | 113 | {"TOS1900", 0}, |
114 | {"", 0}, | |
115 | }; | |
116 | MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); | |
117 | ||
1da177e4 LT |
118 | /* utility |
119 | */ | |
120 | ||
4be44fcd | 121 | static __inline__ void _set_bit(u32 * word, u32 mask, int value) |
1da177e4 LT |
122 | { |
123 | *word = (*word & ~mask) | (mask * value); | |
124 | } | |
125 | ||
126 | /* acpi interface wrappers | |
127 | */ | |
128 | ||
4be44fcd | 129 | static int is_valid_acpi_path(const char *methodName) |
1da177e4 LT |
130 | { |
131 | acpi_handle handle; | |
132 | acpi_status status; | |
133 | ||
4be44fcd | 134 | status = acpi_get_handle(NULL, (char *)methodName, &handle); |
1da177e4 LT |
135 | return !ACPI_FAILURE(status); |
136 | } | |
137 | ||
4be44fcd | 138 | static int write_acpi_int(const char *methodName, int val) |
1da177e4 LT |
139 | { |
140 | struct acpi_object_list params; | |
141 | union acpi_object in_objs[1]; | |
142 | acpi_status status; | |
143 | ||
b2b7910d | 144 | params.count = ARRAY_SIZE(in_objs); |
1da177e4 LT |
145 | params.pointer = in_objs; |
146 | in_objs[0].type = ACPI_TYPE_INTEGER; | |
147 | in_objs[0].integer.value = val; | |
148 | ||
4be44fcd | 149 | status = acpi_evaluate_object(NULL, (char *)methodName, ¶ms, NULL); |
1da177e4 LT |
150 | return (status == AE_OK); |
151 | } | |
152 | ||
153 | #if 0 | |
4be44fcd | 154 | static int read_acpi_int(const char *methodName, int *pVal) |
1da177e4 LT |
155 | { |
156 | struct acpi_buffer results; | |
157 | union acpi_object out_objs[1]; | |
158 | acpi_status status; | |
159 | ||
160 | results.length = sizeof(out_objs); | |
161 | results.pointer = out_objs; | |
162 | ||
4be44fcd | 163 | status = acpi_evaluate_object(0, (char *)methodName, 0, &results); |
1da177e4 LT |
164 | *pVal = out_objs[0].integer.value; |
165 | ||
166 | return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); | |
167 | } | |
168 | #endif | |
169 | ||
4be44fcd | 170 | static const char *method_hci /*= 0*/ ; |
1da177e4 LT |
171 | |
172 | /* Perform a raw HCI call. Here we don't care about input or output buffer | |
173 | * format. | |
174 | */ | |
4be44fcd | 175 | static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) |
1da177e4 LT |
176 | { |
177 | struct acpi_object_list params; | |
178 | union acpi_object in_objs[HCI_WORDS]; | |
179 | struct acpi_buffer results; | |
4be44fcd | 180 | union acpi_object out_objs[HCI_WORDS + 1]; |
1da177e4 LT |
181 | acpi_status status; |
182 | int i; | |
183 | ||
184 | params.count = HCI_WORDS; | |
185 | params.pointer = in_objs; | |
186 | for (i = 0; i < HCI_WORDS; ++i) { | |
187 | in_objs[i].type = ACPI_TYPE_INTEGER; | |
188 | in_objs[i].integer.value = in[i]; | |
189 | } | |
190 | ||
191 | results.length = sizeof(out_objs); | |
192 | results.pointer = out_objs; | |
193 | ||
4be44fcd LB |
194 | status = acpi_evaluate_object(NULL, (char *)method_hci, ¶ms, |
195 | &results); | |
1da177e4 LT |
196 | if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { |
197 | for (i = 0; i < out_objs->package.count; ++i) { | |
198 | out[i] = out_objs->package.elements[i].integer.value; | |
199 | } | |
200 | } | |
201 | ||
202 | return status; | |
203 | } | |
204 | ||
c41a40c5 | 205 | /* common hci tasks (get or set one or two value) |
1da177e4 LT |
206 | * |
207 | * In addition to the ACPI status, the HCI system returns a result which | |
208 | * may be useful (such as "not supported"). | |
209 | */ | |
210 | ||
4be44fcd | 211 | static acpi_status hci_write1(u32 reg, u32 in1, u32 * result) |
1da177e4 LT |
212 | { |
213 | u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; | |
214 | u32 out[HCI_WORDS]; | |
215 | acpi_status status = hci_raw(in, out); | |
216 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
217 | return status; | |
218 | } | |
219 | ||
4be44fcd | 220 | static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) |
1da177e4 LT |
221 | { |
222 | u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; | |
223 | u32 out[HCI_WORDS]; | |
224 | acpi_status status = hci_raw(in, out); | |
225 | *out1 = out[2]; | |
226 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
227 | return status; | |
228 | } | |
229 | ||
c41a40c5 | 230 | static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32 *result) |
231 | { | |
232 | u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; | |
233 | u32 out[HCI_WORDS]; | |
234 | acpi_status status = hci_raw(in, out); | |
235 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
236 | return status; | |
237 | } | |
238 | ||
239 | static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result) | |
240 | { | |
241 | u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; | |
242 | u32 out[HCI_WORDS]; | |
243 | acpi_status status = hci_raw(in, out); | |
244 | *out1 = out[2]; | |
245 | *out2 = out[3]; | |
246 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
247 | return status; | |
248 | } | |
249 | ||
250 | struct toshiba_acpi_dev { | |
251 | struct platform_device *p_dev; | |
19d337df | 252 | struct rfkill *bt_rfk; |
c41a40c5 | 253 | |
254 | const char *bt_name; | |
c41a40c5 | 255 | |
256 | struct mutex mutex; | |
257 | }; | |
258 | ||
259 | static struct toshiba_acpi_dev toshiba_acpi = { | |
260 | .bt_name = "Toshiba Bluetooth", | |
c41a40c5 | 261 | }; |
262 | ||
263 | /* Bluetooth rfkill handlers */ | |
264 | ||
265 | static u32 hci_get_bt_present(bool *present) | |
266 | { | |
267 | u32 hci_result; | |
268 | u32 value, value2; | |
269 | ||
270 | value = 0; | |
271 | value2 = 0; | |
272 | hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); | |
273 | if (hci_result == HCI_SUCCESS) | |
274 | *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; | |
275 | ||
276 | return hci_result; | |
277 | } | |
278 | ||
c41a40c5 | 279 | static u32 hci_get_radio_state(bool *radio_state) |
280 | { | |
281 | u32 hci_result; | |
282 | u32 value, value2; | |
283 | ||
284 | value = 0; | |
285 | value2 = 0x0001; | |
286 | hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); | |
287 | ||
288 | *radio_state = value & HCI_WIRELESS_KILL_SWITCH; | |
289 | return hci_result; | |
290 | } | |
291 | ||
19d337df | 292 | static int bt_rfkill_set_block(void *data, bool blocked) |
c41a40c5 | 293 | { |
19d337df | 294 | struct toshiba_acpi_dev *dev = data; |
c41a40c5 | 295 | u32 result1, result2; |
296 | u32 value; | |
19d337df | 297 | int err; |
c41a40c5 | 298 | bool radio_state; |
c41a40c5 | 299 | |
19d337df | 300 | value = (blocked == false); |
c41a40c5 | 301 | |
19d337df JB |
302 | mutex_lock(&dev->mutex); |
303 | if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) { | |
304 | err = -EBUSY; | |
305 | goto out; | |
306 | } | |
c41a40c5 | 307 | |
19d337df JB |
308 | if (!radio_state) { |
309 | err = 0; | |
310 | goto out; | |
c41a40c5 | 311 | } |
312 | ||
c41a40c5 | 313 | hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); |
314 | hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); | |
c41a40c5 | 315 | |
316 | if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) | |
19d337df JB |
317 | err = -EBUSY; |
318 | else | |
319 | err = 0; | |
320 | out: | |
321 | mutex_unlock(&dev->mutex); | |
322 | return err; | |
c41a40c5 | 323 | } |
324 | ||
19d337df | 325 | static void bt_rfkill_poll(struct rfkill *rfkill, void *data) |
c41a40c5 | 326 | { |
c41a40c5 | 327 | bool new_rfk_state; |
328 | bool value; | |
329 | u32 hci_result; | |
19d337df JB |
330 | struct toshiba_acpi_dev *dev = data; |
331 | ||
332 | mutex_lock(&dev->mutex); | |
c41a40c5 | 333 | |
334 | hci_result = hci_get_radio_state(&value); | |
19d337df JB |
335 | if (hci_result != HCI_SUCCESS) { |
336 | /* Can't do anything useful */ | |
337 | mutex_unlock(&dev->mutex); | |
338 | } | |
c41a40c5 | 339 | |
340 | new_rfk_state = value; | |
341 | ||
c41a40c5 | 342 | mutex_unlock(&dev->mutex); |
343 | ||
19d337df JB |
344 | if (rfkill_set_hw_state(rfkill, !new_rfk_state)) |
345 | bt_rfkill_set_block(data, true); | |
c41a40c5 | 346 | } |
347 | ||
19d337df JB |
348 | static const struct rfkill_ops toshiba_rfk_ops = { |
349 | .set_block = bt_rfkill_set_block, | |
350 | .poll = bt_rfkill_poll, | |
351 | }; | |
352 | ||
4be44fcd | 353 | static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; |
c9263557 | 354 | static struct backlight_device *toshiba_backlight_device; |
4be44fcd LB |
355 | static int force_fan; |
356 | static int last_key_event; | |
357 | static int key_event_valid; | |
1da177e4 | 358 | |
4be44fcd LB |
359 | typedef struct _ProcItem { |
360 | const char *name; | |
361 | char *(*read_func) (char *); | |
362 | unsigned long (*write_func) (const char *, unsigned long); | |
1da177e4 LT |
363 | } ProcItem; |
364 | ||
365 | /* proc file handlers | |
366 | */ | |
367 | ||
368 | static int | |
4be44fcd LB |
369 | dispatch_read(char *page, char **start, off_t off, int count, int *eof, |
370 | ProcItem * item) | |
1da177e4 | 371 | { |
4be44fcd | 372 | char *p = page; |
1da177e4 LT |
373 | int len; |
374 | ||
375 | if (off == 0) | |
376 | p = item->read_func(p); | |
377 | ||
378 | /* ISSUE: I don't understand this code */ | |
379 | len = (p - page); | |
4be44fcd LB |
380 | if (len <= off + count) |
381 | *eof = 1; | |
1da177e4 LT |
382 | *start = page + off; |
383 | len -= off; | |
4be44fcd LB |
384 | if (len > count) |
385 | len = count; | |
386 | if (len < 0) | |
387 | len = 0; | |
1da177e4 LT |
388 | return len; |
389 | } | |
390 | ||
391 | static int | |
4be44fcd LB |
392 | dispatch_write(struct file *file, const char __user * buffer, |
393 | unsigned long count, ProcItem * item) | |
1da177e4 LT |
394 | { |
395 | int result; | |
4be44fcd | 396 | char *tmp_buffer; |
1da177e4 LT |
397 | |
398 | /* Arg buffer points to userspace memory, which can't be accessed | |
399 | * directly. Since we're making a copy, zero-terminate the | |
400 | * destination so that sscanf can be used on it safely. | |
401 | */ | |
402 | tmp_buffer = kmalloc(count + 1, GFP_KERNEL); | |
4be44fcd | 403 | if (!tmp_buffer) |
f4224153 PI |
404 | return -ENOMEM; |
405 | ||
1da177e4 LT |
406 | if (copy_from_user(tmp_buffer, buffer, count)) { |
407 | result = -EFAULT; | |
4be44fcd | 408 | } else { |
1da177e4 LT |
409 | tmp_buffer[count] = 0; |
410 | result = item->write_func(tmp_buffer, count); | |
411 | } | |
412 | kfree(tmp_buffer); | |
413 | return result; | |
414 | } | |
415 | ||
c9263557 | 416 | static int get_lcd(struct backlight_device *bd) |
1da177e4 LT |
417 | { |
418 | u32 hci_result; | |
419 | u32 value; | |
420 | ||
421 | hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); | |
422 | if (hci_result == HCI_SUCCESS) { | |
c9263557 HM |
423 | return (value >> HCI_LCD_BRIGHTNESS_SHIFT); |
424 | } else | |
425 | return -EFAULT; | |
426 | } | |
427 | ||
428 | static char *read_lcd(char *p) | |
429 | { | |
430 | int value = get_lcd(NULL); | |
431 | ||
432 | if (value >= 0) { | |
1da177e4 LT |
433 | p += sprintf(p, "brightness: %d\n", value); |
434 | p += sprintf(p, "brightness_levels: %d\n", | |
4be44fcd | 435 | HCI_LCD_BRIGHTNESS_LEVELS); |
1da177e4 LT |
436 | } else { |
437 | printk(MY_ERR "Error reading LCD brightness\n"); | |
438 | } | |
439 | ||
440 | return p; | |
441 | } | |
442 | ||
c9263557 HM |
443 | static int set_lcd(int value) |
444 | { | |
445 | u32 hci_result; | |
446 | ||
447 | value = value << HCI_LCD_BRIGHTNESS_SHIFT; | |
448 | hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); | |
449 | if (hci_result != HCI_SUCCESS) | |
450 | return -EFAULT; | |
451 | ||
452 | return 0; | |
453 | } | |
454 | ||
455 | static int set_lcd_status(struct backlight_device *bd) | |
456 | { | |
599a52d1 | 457 | return set_lcd(bd->props.brightness); |
c9263557 HM |
458 | } |
459 | ||
4be44fcd | 460 | static unsigned long write_lcd(const char *buffer, unsigned long count) |
1da177e4 LT |
461 | { |
462 | int value; | |
c8af57eb | 463 | int ret; |
1da177e4 LT |
464 | |
465 | if (sscanf(buffer, " brightness : %i", &value) == 1 && | |
c8af57eb | 466 | value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { |
c9263557 | 467 | ret = set_lcd(value); |
c8af57eb MO |
468 | if (ret == 0) |
469 | ret = count; | |
470 | } else { | |
c9263557 | 471 | ret = -EINVAL; |
c8af57eb | 472 | } |
c9263557 | 473 | return ret; |
1da177e4 LT |
474 | } |
475 | ||
4be44fcd | 476 | static char *read_video(char *p) |
1da177e4 LT |
477 | { |
478 | u32 hci_result; | |
479 | u32 value; | |
480 | ||
481 | hci_read1(HCI_VIDEO_OUT, &value, &hci_result); | |
482 | if (hci_result == HCI_SUCCESS) { | |
483 | int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; | |
484 | int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; | |
4be44fcd | 485 | int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; |
1da177e4 LT |
486 | p += sprintf(p, "lcd_out: %d\n", is_lcd); |
487 | p += sprintf(p, "crt_out: %d\n", is_crt); | |
488 | p += sprintf(p, "tv_out: %d\n", is_tv); | |
489 | } else { | |
490 | printk(MY_ERR "Error reading video out status\n"); | |
491 | } | |
492 | ||
493 | return p; | |
494 | } | |
495 | ||
4be44fcd | 496 | static unsigned long write_video(const char *buffer, unsigned long count) |
1da177e4 LT |
497 | { |
498 | int value; | |
499 | int remain = count; | |
500 | int lcd_out = -1; | |
501 | int crt_out = -1; | |
502 | int tv_out = -1; | |
503 | u32 hci_result; | |
b4482a4b | 504 | u32 video_out; |
1da177e4 LT |
505 | |
506 | /* scan expression. Multiple expressions may be delimited with ; | |
507 | * | |
508 | * NOTE: to keep scanning simple, invalid fields are ignored | |
509 | */ | |
510 | while (remain) { | |
511 | if (sscanf(buffer, " lcd_out : %i", &value) == 1) | |
512 | lcd_out = value & 1; | |
513 | else if (sscanf(buffer, " crt_out : %i", &value) == 1) | |
514 | crt_out = value & 1; | |
515 | else if (sscanf(buffer, " tv_out : %i", &value) == 1) | |
516 | tv_out = value & 1; | |
517 | /* advance to one character past the next ; */ | |
518 | do { | |
519 | ++buffer; | |
520 | --remain; | |
521 | } | |
4be44fcd | 522 | while (remain && *(buffer - 1) != ';'); |
1da177e4 LT |
523 | } |
524 | ||
525 | hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); | |
526 | if (hci_result == HCI_SUCCESS) { | |
9e113e00 | 527 | unsigned int new_video_out = video_out; |
1da177e4 LT |
528 | if (lcd_out != -1) |
529 | _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); | |
530 | if (crt_out != -1) | |
531 | _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); | |
532 | if (tv_out != -1) | |
533 | _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); | |
534 | /* To avoid unnecessary video disruption, only write the new | |
535 | * video setting if something changed. */ | |
536 | if (new_video_out != video_out) | |
537 | write_acpi_int(METHOD_VIDEO_OUT, new_video_out); | |
538 | } else { | |
539 | return -EFAULT; | |
540 | } | |
541 | ||
542 | return count; | |
543 | } | |
544 | ||
4be44fcd | 545 | static char *read_fan(char *p) |
1da177e4 LT |
546 | { |
547 | u32 hci_result; | |
548 | u32 value; | |
549 | ||
550 | hci_read1(HCI_FAN, &value, &hci_result); | |
551 | if (hci_result == HCI_SUCCESS) { | |
552 | p += sprintf(p, "running: %d\n", (value > 0)); | |
553 | p += sprintf(p, "force_on: %d\n", force_fan); | |
554 | } else { | |
555 | printk(MY_ERR "Error reading fan status\n"); | |
556 | } | |
557 | ||
558 | return p; | |
559 | } | |
560 | ||
4be44fcd | 561 | static unsigned long write_fan(const char *buffer, unsigned long count) |
1da177e4 LT |
562 | { |
563 | int value; | |
564 | u32 hci_result; | |
565 | ||
566 | if (sscanf(buffer, " force_on : %i", &value) == 1 && | |
4be44fcd | 567 | value >= 0 && value <= 1) { |
1da177e4 LT |
568 | hci_write1(HCI_FAN, value, &hci_result); |
569 | if (hci_result != HCI_SUCCESS) | |
570 | return -EFAULT; | |
571 | else | |
572 | force_fan = value; | |
573 | } else { | |
574 | return -EINVAL; | |
575 | } | |
576 | ||
577 | return count; | |
578 | } | |
579 | ||
4be44fcd | 580 | static char *read_keys(char *p) |
1da177e4 LT |
581 | { |
582 | u32 hci_result; | |
583 | u32 value; | |
584 | ||
585 | if (!key_event_valid) { | |
586 | hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); | |
587 | if (hci_result == HCI_SUCCESS) { | |
588 | key_event_valid = 1; | |
589 | last_key_event = value; | |
590 | } else if (hci_result == HCI_EMPTY) { | |
591 | /* better luck next time */ | |
592 | } else if (hci_result == HCI_NOT_SUPPORTED) { | |
593 | /* This is a workaround for an unresolved issue on | |
594 | * some machines where system events sporadically | |
595 | * become disabled. */ | |
596 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | |
597 | printk(MY_NOTICE "Re-enabled hotkeys\n"); | |
598 | } else { | |
599 | printk(MY_ERR "Error reading hotkey status\n"); | |
600 | goto end; | |
601 | } | |
602 | } | |
603 | ||
604 | p += sprintf(p, "hotkey_ready: %d\n", key_event_valid); | |
605 | p += sprintf(p, "hotkey: 0x%04x\n", last_key_event); | |
606 | ||
4be44fcd | 607 | end: |
1da177e4 LT |
608 | return p; |
609 | } | |
610 | ||
4be44fcd | 611 | static unsigned long write_keys(const char *buffer, unsigned long count) |
1da177e4 LT |
612 | { |
613 | int value; | |
614 | ||
4be44fcd | 615 | if (sscanf(buffer, " hotkey_ready : %i", &value) == 1 && value == 0) { |
1da177e4 LT |
616 | key_event_valid = 0; |
617 | } else { | |
618 | return -EINVAL; | |
619 | } | |
620 | ||
621 | return count; | |
622 | } | |
623 | ||
4be44fcd | 624 | static char *read_version(char *p) |
1da177e4 LT |
625 | { |
626 | p += sprintf(p, "driver: %s\n", TOSHIBA_ACPI_VERSION); | |
627 | p += sprintf(p, "proc_interface: %d\n", | |
4be44fcd | 628 | PROC_INTERFACE_VERSION); |
1da177e4 LT |
629 | return p; |
630 | } | |
631 | ||
632 | /* proc and module init | |
633 | */ | |
634 | ||
635 | #define PROC_TOSHIBA "toshiba" | |
636 | ||
4be44fcd LB |
637 | static ProcItem proc_items[] = { |
638 | {"lcd", read_lcd, write_lcd}, | |
639 | {"video", read_video, write_video}, | |
640 | {"fan", read_fan, write_fan}, | |
641 | {"keys", read_keys, write_keys}, | |
642 | {"version", read_version, NULL}, | |
643 | {NULL} | |
1da177e4 LT |
644 | }; |
645 | ||
4be44fcd | 646 | static acpi_status __init add_device(void) |
1da177e4 | 647 | { |
4be44fcd LB |
648 | struct proc_dir_entry *proc; |
649 | ProcItem *item; | |
1da177e4 | 650 | |
4be44fcd | 651 | for (item = proc_items; item->name; ++item) { |
1da177e4 | 652 | proc = create_proc_read_entry(item->name, |
4be44fcd LB |
653 | S_IFREG | S_IRUGO | S_IWUSR, |
654 | toshiba_proc_dir, | |
655 | (read_proc_t *) dispatch_read, | |
656 | item); | |
1da177e4 | 657 | if (proc && item->write_func) |
4be44fcd | 658 | proc->write_proc = (write_proc_t *) dispatch_write; |
1da177e4 LT |
659 | } |
660 | ||
661 | return AE_OK; | |
662 | } | |
663 | ||
b1d93de3 | 664 | static acpi_status remove_device(void) |
1da177e4 | 665 | { |
4be44fcd | 666 | ProcItem *item; |
1da177e4 LT |
667 | |
668 | for (item = proc_items; item->name; ++item) | |
669 | remove_proc_entry(item->name, toshiba_proc_dir); | |
670 | return AE_OK; | |
671 | } | |
672 | ||
599a52d1 | 673 | static struct backlight_ops toshiba_backlight_data = { |
c9263557 HM |
674 | .get_brightness = get_lcd, |
675 | .update_status = set_lcd_status, | |
c9263557 HM |
676 | }; |
677 | ||
b2b77b23 | 678 | static void toshiba_acpi_exit(void) |
c9263557 | 679 | { |
19d337df JB |
680 | if (toshiba_acpi.bt_rfk) { |
681 | rfkill_unregister(toshiba_acpi.bt_rfk); | |
682 | rfkill_destroy(toshiba_acpi.bt_rfk); | |
c41a40c5 | 683 | } |
684 | ||
c9263557 HM |
685 | if (toshiba_backlight_device) |
686 | backlight_device_unregister(toshiba_backlight_device); | |
687 | ||
688 | remove_device(); | |
689 | ||
690 | if (toshiba_proc_dir) | |
691 | remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); | |
692 | ||
c41a40c5 | 693 | platform_device_unregister(toshiba_acpi.p_dev); |
694 | ||
c9263557 HM |
695 | return; |
696 | } | |
697 | ||
4be44fcd | 698 | static int __init toshiba_acpi_init(void) |
1da177e4 LT |
699 | { |
700 | acpi_status status = AE_OK; | |
701 | u32 hci_result; | |
c41a40c5 | 702 | bool bt_present; |
c41a40c5 | 703 | int ret = 0; |
1da177e4 LT |
704 | |
705 | if (acpi_disabled) | |
706 | return -ENODEV; | |
fb9802fa | 707 | |
1da177e4 LT |
708 | /* simple device detection: look for HCI method */ |
709 | if (is_valid_acpi_path(METHOD_HCI_1)) | |
710 | method_hci = METHOD_HCI_1; | |
711 | else if (is_valid_acpi_path(METHOD_HCI_2)) | |
712 | method_hci = METHOD_HCI_2; | |
713 | else | |
714 | return -ENODEV; | |
715 | ||
716 | printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", | |
4be44fcd | 717 | TOSHIBA_ACPI_VERSION); |
1da177e4 LT |
718 | printk(MY_INFO " HCI method: %s\n", method_hci); |
719 | ||
c41a40c5 | 720 | mutex_init(&toshiba_acpi.mutex); |
721 | ||
722 | toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi", | |
723 | -1, NULL, 0); | |
724 | if (IS_ERR(toshiba_acpi.p_dev)) { | |
725 | ret = PTR_ERR(toshiba_acpi.p_dev); | |
726 | printk(MY_ERR "unable to register platform device\n"); | |
727 | toshiba_acpi.p_dev = NULL; | |
728 | toshiba_acpi_exit(); | |
729 | return ret; | |
730 | } | |
731 | ||
1da177e4 LT |
732 | force_fan = 0; |
733 | key_event_valid = 0; | |
734 | ||
735 | /* enable event fifo */ | |
736 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | |
737 | ||
738 | toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); | |
739 | if (!toshiba_proc_dir) { | |
c41a40c5 | 740 | toshiba_acpi_exit(); |
741 | return -ENODEV; | |
1da177e4 | 742 | } else { |
1da177e4 | 743 | status = add_device(); |
c41a40c5 | 744 | if (ACPI_FAILURE(status)) { |
745 | toshiba_acpi_exit(); | |
746 | return -ENODEV; | |
747 | } | |
1da177e4 LT |
748 | } |
749 | ||
c41a40c5 | 750 | toshiba_backlight_device = backlight_device_register("toshiba", |
751 | &toshiba_acpi.p_dev->dev, | |
519ab5f2 | 752 | NULL, |
c9263557 HM |
753 | &toshiba_backlight_data); |
754 | if (IS_ERR(toshiba_backlight_device)) { | |
c41a40c5 | 755 | ret = PTR_ERR(toshiba_backlight_device); |
1299342b | 756 | |
c9263557 HM |
757 | printk(KERN_ERR "Could not register toshiba backlight device\n"); |
758 | toshiba_backlight_device = NULL; | |
759 | toshiba_acpi_exit(); | |
1299342b | 760 | return ret; |
c9263557 | 761 | } |
599a52d1 | 762 | toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; |
1da177e4 | 763 | |
c41a40c5 | 764 | /* Register rfkill switch for Bluetooth */ |
765 | if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { | |
19d337df JB |
766 | toshiba_acpi.bt_rfk = rfkill_alloc(toshiba_acpi.bt_name, |
767 | &toshiba_acpi.p_dev->dev, | |
768 | RFKILL_TYPE_BLUETOOTH, | |
769 | &toshiba_rfk_ops, | |
770 | &toshiba_acpi); | |
771 | if (!toshiba_acpi.bt_rfk) { | |
c41a40c5 | 772 | printk(MY_ERR "unable to allocate rfkill device\n"); |
773 | toshiba_acpi_exit(); | |
774 | return -ENOMEM; | |
775 | } | |
776 | ||
19d337df | 777 | ret = rfkill_register(toshiba_acpi.bt_rfk); |
c41a40c5 | 778 | if (ret) { |
779 | printk(MY_ERR "unable to register rfkill device\n"); | |
19d337df | 780 | rfkill_destroy(toshiba_acpi.bt_rfk); |
38aefbc5 FD |
781 | toshiba_acpi_exit(); |
782 | return ret; | |
783 | } | |
c41a40c5 | 784 | } |
785 | ||
786 | return 0; | |
1da177e4 LT |
787 | } |
788 | ||
789 | module_init(toshiba_acpi_init); | |
790 | module_exit(toshiba_acpi_exit); |