]>
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> | |
936c8bcd | 45 | #include <linux/seq_file.h> |
c9263557 | 46 | #include <linux/backlight.h> |
c41a40c5 | 47 | #include <linux/platform_device.h> |
48 | #include <linux/rfkill.h> | |
6335e4d5 | 49 | #include <linux/input.h> |
5a0e3ad6 | 50 | #include <linux/slab.h> |
c9263557 | 51 | |
1da177e4 LT |
52 | #include <asm/uaccess.h> |
53 | ||
54 | #include <acpi/acpi_drivers.h> | |
55 | ||
56 | MODULE_AUTHOR("John Belmonte"); | |
57 | MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); | |
58 | MODULE_LICENSE("GPL"); | |
59 | ||
60 | #define MY_LOGPREFIX "toshiba_acpi: " | |
61 | #define MY_ERR KERN_ERR MY_LOGPREFIX | |
62 | #define MY_NOTICE KERN_NOTICE MY_LOGPREFIX | |
63 | #define MY_INFO KERN_INFO MY_LOGPREFIX | |
64 | ||
65 | /* Toshiba ACPI method paths */ | |
66 | #define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" | |
6335e4d5 MG |
67 | #define TOSH_INTERFACE_1 "\\_SB_.VALD" |
68 | #define TOSH_INTERFACE_2 "\\_SB_.VALZ" | |
1da177e4 | 69 | #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" |
6335e4d5 | 70 | #define GHCI_METHOD ".GHCI" |
1da177e4 LT |
71 | |
72 | /* Toshiba HCI interface definitions | |
73 | * | |
74 | * HCI is Toshiba's "Hardware Control Interface" which is supposed to | |
75 | * be uniform across all their models. Ideally we would just call | |
76 | * dedicated ACPI methods instead of using this primitive interface. | |
77 | * However the ACPI methods seem to be incomplete in some areas (for | |
78 | * example they allow setting, but not reading, the LCD brightness value), | |
79 | * so this is still useful. | |
80 | */ | |
81 | ||
82 | #define HCI_WORDS 6 | |
83 | ||
84 | /* operations */ | |
85 | #define HCI_SET 0xff00 | |
86 | #define HCI_GET 0xfe00 | |
87 | ||
88 | /* return codes */ | |
89 | #define HCI_SUCCESS 0x0000 | |
90 | #define HCI_FAILURE 0x1000 | |
91 | #define HCI_NOT_SUPPORTED 0x8000 | |
92 | #define HCI_EMPTY 0x8c00 | |
93 | ||
94 | /* registers */ | |
95 | #define HCI_FAN 0x0004 | |
96 | #define HCI_SYSTEM_EVENT 0x0016 | |
97 | #define HCI_VIDEO_OUT 0x001c | |
98 | #define HCI_HOTKEY_EVENT 0x001e | |
99 | #define HCI_LCD_BRIGHTNESS 0x002a | |
c41a40c5 | 100 | #define HCI_WIRELESS 0x0056 |
1da177e4 LT |
101 | |
102 | /* field definitions */ | |
103 | #define HCI_LCD_BRIGHTNESS_BITS 3 | |
104 | #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) | |
105 | #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) | |
106 | #define HCI_VIDEO_OUT_LCD 0x1 | |
107 | #define HCI_VIDEO_OUT_CRT 0x2 | |
108 | #define HCI_VIDEO_OUT_TV 0x4 | |
c41a40c5 | 109 | #define HCI_WIRELESS_KILL_SWITCH 0x01 |
110 | #define HCI_WIRELESS_BT_PRESENT 0x0f | |
111 | #define HCI_WIRELESS_BT_ATTACH 0x40 | |
112 | #define HCI_WIRELESS_BT_POWER 0x80 | |
1da177e4 | 113 | |
4db42c51 | 114 | static const struct acpi_device_id toshiba_device_ids[] = { |
115 | {"TOS6200", 0}, | |
c41a40c5 | 116 | {"TOS6208", 0}, |
4db42c51 | 117 | {"TOS1900", 0}, |
118 | {"", 0}, | |
119 | }; | |
120 | MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); | |
121 | ||
6335e4d5 MG |
122 | struct key_entry { |
123 | char type; | |
124 | u16 code; | |
125 | u16 keycode; | |
126 | }; | |
127 | ||
128 | enum {KE_KEY, KE_END}; | |
129 | ||
130 | static struct key_entry toshiba_acpi_keymap[] = { | |
131 | {KE_KEY, 0x101, KEY_MUTE}, | |
132 | {KE_KEY, 0x13b, KEY_COFFEE}, | |
133 | {KE_KEY, 0x13c, KEY_BATTERY}, | |
134 | {KE_KEY, 0x13d, KEY_SLEEP}, | |
135 | {KE_KEY, 0x13e, KEY_SUSPEND}, | |
136 | {KE_KEY, 0x13f, KEY_SWITCHVIDEOMODE}, | |
137 | {KE_KEY, 0x140, KEY_BRIGHTNESSDOWN}, | |
138 | {KE_KEY, 0x141, KEY_BRIGHTNESSUP}, | |
139 | {KE_KEY, 0x142, KEY_WLAN}, | |
140 | {KE_KEY, 0x143, KEY_PROG1}, | |
141 | {KE_KEY, 0xb05, KEY_PROG2}, | |
142 | {KE_KEY, 0xb06, KEY_WWW}, | |
143 | {KE_KEY, 0xb07, KEY_MAIL}, | |
144 | {KE_KEY, 0xb30, KEY_STOP}, | |
145 | {KE_KEY, 0xb31, KEY_PREVIOUSSONG}, | |
146 | {KE_KEY, 0xb32, KEY_NEXTSONG}, | |
147 | {KE_KEY, 0xb33, KEY_PLAYPAUSE}, | |
148 | {KE_KEY, 0xb5a, KEY_MEDIA}, | |
149 | {KE_END, 0, 0}, | |
150 | }; | |
151 | ||
1da177e4 LT |
152 | /* utility |
153 | */ | |
154 | ||
4be44fcd | 155 | static __inline__ void _set_bit(u32 * word, u32 mask, int value) |
1da177e4 LT |
156 | { |
157 | *word = (*word & ~mask) | (mask * value); | |
158 | } | |
159 | ||
160 | /* acpi interface wrappers | |
161 | */ | |
162 | ||
4be44fcd | 163 | static int is_valid_acpi_path(const char *methodName) |
1da177e4 LT |
164 | { |
165 | acpi_handle handle; | |
166 | acpi_status status; | |
167 | ||
4be44fcd | 168 | status = acpi_get_handle(NULL, (char *)methodName, &handle); |
1da177e4 LT |
169 | return !ACPI_FAILURE(status); |
170 | } | |
171 | ||
4be44fcd | 172 | static int write_acpi_int(const char *methodName, int val) |
1da177e4 LT |
173 | { |
174 | struct acpi_object_list params; | |
175 | union acpi_object in_objs[1]; | |
176 | acpi_status status; | |
177 | ||
b2b7910d | 178 | params.count = ARRAY_SIZE(in_objs); |
1da177e4 LT |
179 | params.pointer = in_objs; |
180 | in_objs[0].type = ACPI_TYPE_INTEGER; | |
181 | in_objs[0].integer.value = val; | |
182 | ||
4be44fcd | 183 | status = acpi_evaluate_object(NULL, (char *)methodName, ¶ms, NULL); |
1da177e4 LT |
184 | return (status == AE_OK); |
185 | } | |
186 | ||
187 | #if 0 | |
4be44fcd | 188 | static int read_acpi_int(const char *methodName, int *pVal) |
1da177e4 LT |
189 | { |
190 | struct acpi_buffer results; | |
191 | union acpi_object out_objs[1]; | |
192 | acpi_status status; | |
193 | ||
194 | results.length = sizeof(out_objs); | |
195 | results.pointer = out_objs; | |
196 | ||
4be44fcd | 197 | status = acpi_evaluate_object(0, (char *)methodName, 0, &results); |
1da177e4 LT |
198 | *pVal = out_objs[0].integer.value; |
199 | ||
200 | return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); | |
201 | } | |
202 | #endif | |
203 | ||
4be44fcd | 204 | static const char *method_hci /*= 0*/ ; |
1da177e4 LT |
205 | |
206 | /* Perform a raw HCI call. Here we don't care about input or output buffer | |
207 | * format. | |
208 | */ | |
4be44fcd | 209 | static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) |
1da177e4 LT |
210 | { |
211 | struct acpi_object_list params; | |
212 | union acpi_object in_objs[HCI_WORDS]; | |
213 | struct acpi_buffer results; | |
4be44fcd | 214 | union acpi_object out_objs[HCI_WORDS + 1]; |
1da177e4 LT |
215 | acpi_status status; |
216 | int i; | |
217 | ||
218 | params.count = HCI_WORDS; | |
219 | params.pointer = in_objs; | |
220 | for (i = 0; i < HCI_WORDS; ++i) { | |
221 | in_objs[i].type = ACPI_TYPE_INTEGER; | |
222 | in_objs[i].integer.value = in[i]; | |
223 | } | |
224 | ||
225 | results.length = sizeof(out_objs); | |
226 | results.pointer = out_objs; | |
227 | ||
4be44fcd LB |
228 | status = acpi_evaluate_object(NULL, (char *)method_hci, ¶ms, |
229 | &results); | |
1da177e4 LT |
230 | if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { |
231 | for (i = 0; i < out_objs->package.count; ++i) { | |
232 | out[i] = out_objs->package.elements[i].integer.value; | |
233 | } | |
234 | } | |
235 | ||
236 | return status; | |
237 | } | |
238 | ||
c41a40c5 | 239 | /* common hci tasks (get or set one or two value) |
1da177e4 LT |
240 | * |
241 | * In addition to the ACPI status, the HCI system returns a result which | |
242 | * may be useful (such as "not supported"). | |
243 | */ | |
244 | ||
4be44fcd | 245 | static acpi_status hci_write1(u32 reg, u32 in1, u32 * result) |
1da177e4 LT |
246 | { |
247 | u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; | |
248 | u32 out[HCI_WORDS]; | |
249 | acpi_status status = hci_raw(in, out); | |
250 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
251 | return status; | |
252 | } | |
253 | ||
4be44fcd | 254 | static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) |
1da177e4 LT |
255 | { |
256 | u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; | |
257 | u32 out[HCI_WORDS]; | |
258 | acpi_status status = hci_raw(in, out); | |
259 | *out1 = out[2]; | |
260 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
261 | return status; | |
262 | } | |
263 | ||
c41a40c5 | 264 | static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32 *result) |
265 | { | |
266 | u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; | |
267 | u32 out[HCI_WORDS]; | |
268 | acpi_status status = hci_raw(in, out); | |
269 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
270 | return status; | |
271 | } | |
272 | ||
273 | static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result) | |
274 | { | |
275 | u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; | |
276 | u32 out[HCI_WORDS]; | |
277 | acpi_status status = hci_raw(in, out); | |
278 | *out1 = out[2]; | |
279 | *out2 = out[3]; | |
280 | *result = (status == AE_OK) ? out[0] : HCI_FAILURE; | |
281 | return status; | |
282 | } | |
283 | ||
284 | struct toshiba_acpi_dev { | |
285 | struct platform_device *p_dev; | |
19d337df | 286 | struct rfkill *bt_rfk; |
6335e4d5 MG |
287 | struct input_dev *hotkey_dev; |
288 | acpi_handle handle; | |
c41a40c5 | 289 | |
290 | const char *bt_name; | |
c41a40c5 | 291 | |
292 | struct mutex mutex; | |
293 | }; | |
294 | ||
295 | static struct toshiba_acpi_dev toshiba_acpi = { | |
296 | .bt_name = "Toshiba Bluetooth", | |
c41a40c5 | 297 | }; |
298 | ||
299 | /* Bluetooth rfkill handlers */ | |
300 | ||
301 | static u32 hci_get_bt_present(bool *present) | |
302 | { | |
303 | u32 hci_result; | |
304 | u32 value, value2; | |
305 | ||
306 | value = 0; | |
307 | value2 = 0; | |
308 | hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); | |
309 | if (hci_result == HCI_SUCCESS) | |
310 | *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; | |
311 | ||
312 | return hci_result; | |
313 | } | |
314 | ||
c41a40c5 | 315 | static u32 hci_get_radio_state(bool *radio_state) |
316 | { | |
317 | u32 hci_result; | |
318 | u32 value, value2; | |
319 | ||
320 | value = 0; | |
321 | value2 = 0x0001; | |
322 | hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); | |
323 | ||
324 | *radio_state = value & HCI_WIRELESS_KILL_SWITCH; | |
325 | return hci_result; | |
326 | } | |
327 | ||
19d337df | 328 | static int bt_rfkill_set_block(void *data, bool blocked) |
c41a40c5 | 329 | { |
19d337df | 330 | struct toshiba_acpi_dev *dev = data; |
c41a40c5 | 331 | u32 result1, result2; |
332 | u32 value; | |
19d337df | 333 | int err; |
c41a40c5 | 334 | bool radio_state; |
c41a40c5 | 335 | |
19d337df | 336 | value = (blocked == false); |
c41a40c5 | 337 | |
19d337df JB |
338 | mutex_lock(&dev->mutex); |
339 | if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) { | |
340 | err = -EBUSY; | |
341 | goto out; | |
342 | } | |
c41a40c5 | 343 | |
19d337df JB |
344 | if (!radio_state) { |
345 | err = 0; | |
346 | goto out; | |
c41a40c5 | 347 | } |
348 | ||
c41a40c5 | 349 | hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); |
350 | hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); | |
c41a40c5 | 351 | |
352 | if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) | |
19d337df JB |
353 | err = -EBUSY; |
354 | else | |
355 | err = 0; | |
356 | out: | |
357 | mutex_unlock(&dev->mutex); | |
358 | return err; | |
c41a40c5 | 359 | } |
360 | ||
19d337df | 361 | static void bt_rfkill_poll(struct rfkill *rfkill, void *data) |
c41a40c5 | 362 | { |
c41a40c5 | 363 | bool new_rfk_state; |
364 | bool value; | |
365 | u32 hci_result; | |
19d337df JB |
366 | struct toshiba_acpi_dev *dev = data; |
367 | ||
368 | mutex_lock(&dev->mutex); | |
c41a40c5 | 369 | |
370 | hci_result = hci_get_radio_state(&value); | |
19d337df JB |
371 | if (hci_result != HCI_SUCCESS) { |
372 | /* Can't do anything useful */ | |
373 | mutex_unlock(&dev->mutex); | |
82e7784f | 374 | return; |
19d337df | 375 | } |
c41a40c5 | 376 | |
377 | new_rfk_state = value; | |
378 | ||
c41a40c5 | 379 | mutex_unlock(&dev->mutex); |
380 | ||
19d337df JB |
381 | if (rfkill_set_hw_state(rfkill, !new_rfk_state)) |
382 | bt_rfkill_set_block(data, true); | |
c41a40c5 | 383 | } |
384 | ||
19d337df JB |
385 | static const struct rfkill_ops toshiba_rfk_ops = { |
386 | .set_block = bt_rfkill_set_block, | |
387 | .poll = bt_rfkill_poll, | |
388 | }; | |
389 | ||
4be44fcd | 390 | static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; |
c9263557 | 391 | static struct backlight_device *toshiba_backlight_device; |
4be44fcd LB |
392 | static int force_fan; |
393 | static int last_key_event; | |
394 | static int key_event_valid; | |
1da177e4 | 395 | |
c9263557 | 396 | static int get_lcd(struct backlight_device *bd) |
1da177e4 LT |
397 | { |
398 | u32 hci_result; | |
399 | u32 value; | |
400 | ||
401 | hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); | |
402 | if (hci_result == HCI_SUCCESS) { | |
c9263557 HM |
403 | return (value >> HCI_LCD_BRIGHTNESS_SHIFT); |
404 | } else | |
405 | return -EFAULT; | |
406 | } | |
407 | ||
936c8bcd | 408 | static int lcd_proc_show(struct seq_file *m, void *v) |
c9263557 HM |
409 | { |
410 | int value = get_lcd(NULL); | |
411 | ||
412 | if (value >= 0) { | |
936c8bcd AD |
413 | seq_printf(m, "brightness: %d\n", value); |
414 | seq_printf(m, "brightness_levels: %d\n", | |
4be44fcd | 415 | HCI_LCD_BRIGHTNESS_LEVELS); |
1da177e4 LT |
416 | } else { |
417 | printk(MY_ERR "Error reading LCD brightness\n"); | |
418 | } | |
419 | ||
936c8bcd AD |
420 | return 0; |
421 | } | |
422 | ||
423 | static int lcd_proc_open(struct inode *inode, struct file *file) | |
424 | { | |
425 | return single_open(file, lcd_proc_show, NULL); | |
1da177e4 LT |
426 | } |
427 | ||
c9263557 HM |
428 | static int set_lcd(int value) |
429 | { | |
430 | u32 hci_result; | |
431 | ||
432 | value = value << HCI_LCD_BRIGHTNESS_SHIFT; | |
433 | hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); | |
434 | if (hci_result != HCI_SUCCESS) | |
435 | return -EFAULT; | |
436 | ||
437 | return 0; | |
438 | } | |
439 | ||
440 | static int set_lcd_status(struct backlight_device *bd) | |
441 | { | |
599a52d1 | 442 | return set_lcd(bd->props.brightness); |
c9263557 HM |
443 | } |
444 | ||
936c8bcd AD |
445 | static ssize_t lcd_proc_write(struct file *file, const char __user *buf, |
446 | size_t count, loff_t *pos) | |
1da177e4 | 447 | { |
936c8bcd AD |
448 | char cmd[42]; |
449 | size_t len; | |
1da177e4 | 450 | int value; |
c8af57eb | 451 | int ret; |
1da177e4 | 452 | |
936c8bcd AD |
453 | len = min(count, sizeof(cmd) - 1); |
454 | if (copy_from_user(cmd, buf, len)) | |
455 | return -EFAULT; | |
456 | cmd[len] = '\0'; | |
457 | ||
458 | if (sscanf(cmd, " brightness : %i", &value) == 1 && | |
c8af57eb | 459 | value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { |
c9263557 | 460 | ret = set_lcd(value); |
c8af57eb MO |
461 | if (ret == 0) |
462 | ret = count; | |
463 | } else { | |
c9263557 | 464 | ret = -EINVAL; |
c8af57eb | 465 | } |
c9263557 | 466 | return ret; |
1da177e4 LT |
467 | } |
468 | ||
936c8bcd AD |
469 | static const struct file_operations lcd_proc_fops = { |
470 | .owner = THIS_MODULE, | |
471 | .open = lcd_proc_open, | |
472 | .read = seq_read, | |
473 | .llseek = seq_lseek, | |
474 | .release = single_release, | |
475 | .write = lcd_proc_write, | |
476 | }; | |
477 | ||
478 | static int video_proc_show(struct seq_file *m, void *v) | |
1da177e4 LT |
479 | { |
480 | u32 hci_result; | |
481 | u32 value; | |
482 | ||
483 | hci_read1(HCI_VIDEO_OUT, &value, &hci_result); | |
484 | if (hci_result == HCI_SUCCESS) { | |
485 | int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; | |
486 | int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; | |
4be44fcd | 487 | int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; |
936c8bcd AD |
488 | seq_printf(m, "lcd_out: %d\n", is_lcd); |
489 | seq_printf(m, "crt_out: %d\n", is_crt); | |
490 | seq_printf(m, "tv_out: %d\n", is_tv); | |
1da177e4 LT |
491 | } else { |
492 | printk(MY_ERR "Error reading video out status\n"); | |
493 | } | |
494 | ||
936c8bcd | 495 | return 0; |
1da177e4 LT |
496 | } |
497 | ||
936c8bcd | 498 | static int video_proc_open(struct inode *inode, struct file *file) |
1da177e4 | 499 | { |
936c8bcd AD |
500 | return single_open(file, video_proc_show, NULL); |
501 | } | |
502 | ||
503 | static ssize_t video_proc_write(struct file *file, const char __user *buf, | |
504 | size_t count, loff_t *pos) | |
505 | { | |
506 | char *cmd, *buffer; | |
1da177e4 LT |
507 | int value; |
508 | int remain = count; | |
509 | int lcd_out = -1; | |
510 | int crt_out = -1; | |
511 | int tv_out = -1; | |
512 | u32 hci_result; | |
b4482a4b | 513 | u32 video_out; |
1da177e4 | 514 | |
936c8bcd AD |
515 | cmd = kmalloc(count + 1, GFP_KERNEL); |
516 | if (!cmd) | |
517 | return -ENOMEM; | |
518 | if (copy_from_user(cmd, buf, count)) { | |
519 | kfree(cmd); | |
520 | return -EFAULT; | |
521 | } | |
522 | cmd[count] = '\0'; | |
523 | ||
524 | buffer = cmd; | |
525 | ||
1da177e4 LT |
526 | /* scan expression. Multiple expressions may be delimited with ; |
527 | * | |
528 | * NOTE: to keep scanning simple, invalid fields are ignored | |
529 | */ | |
530 | while (remain) { | |
531 | if (sscanf(buffer, " lcd_out : %i", &value) == 1) | |
532 | lcd_out = value & 1; | |
533 | else if (sscanf(buffer, " crt_out : %i", &value) == 1) | |
534 | crt_out = value & 1; | |
535 | else if (sscanf(buffer, " tv_out : %i", &value) == 1) | |
536 | tv_out = value & 1; | |
537 | /* advance to one character past the next ; */ | |
538 | do { | |
539 | ++buffer; | |
540 | --remain; | |
541 | } | |
4be44fcd | 542 | while (remain && *(buffer - 1) != ';'); |
1da177e4 LT |
543 | } |
544 | ||
936c8bcd AD |
545 | kfree(cmd); |
546 | ||
1da177e4 LT |
547 | hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); |
548 | if (hci_result == HCI_SUCCESS) { | |
9e113e00 | 549 | unsigned int new_video_out = video_out; |
1da177e4 LT |
550 | if (lcd_out != -1) |
551 | _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); | |
552 | if (crt_out != -1) | |
553 | _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); | |
554 | if (tv_out != -1) | |
555 | _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); | |
556 | /* To avoid unnecessary video disruption, only write the new | |
557 | * video setting if something changed. */ | |
558 | if (new_video_out != video_out) | |
559 | write_acpi_int(METHOD_VIDEO_OUT, new_video_out); | |
560 | } else { | |
561 | return -EFAULT; | |
562 | } | |
563 | ||
564 | return count; | |
565 | } | |
566 | ||
936c8bcd AD |
567 | static const struct file_operations video_proc_fops = { |
568 | .owner = THIS_MODULE, | |
569 | .open = video_proc_open, | |
570 | .read = seq_read, | |
571 | .llseek = seq_lseek, | |
572 | .release = single_release, | |
573 | .write = video_proc_write, | |
574 | }; | |
575 | ||
576 | static int fan_proc_show(struct seq_file *m, void *v) | |
1da177e4 LT |
577 | { |
578 | u32 hci_result; | |
579 | u32 value; | |
580 | ||
581 | hci_read1(HCI_FAN, &value, &hci_result); | |
582 | if (hci_result == HCI_SUCCESS) { | |
936c8bcd AD |
583 | seq_printf(m, "running: %d\n", (value > 0)); |
584 | seq_printf(m, "force_on: %d\n", force_fan); | |
1da177e4 LT |
585 | } else { |
586 | printk(MY_ERR "Error reading fan status\n"); | |
587 | } | |
588 | ||
936c8bcd AD |
589 | return 0; |
590 | } | |
591 | ||
592 | static int fan_proc_open(struct inode *inode, struct file *file) | |
593 | { | |
594 | return single_open(file, fan_proc_show, NULL); | |
1da177e4 LT |
595 | } |
596 | ||
936c8bcd AD |
597 | static ssize_t fan_proc_write(struct file *file, const char __user *buf, |
598 | size_t count, loff_t *pos) | |
1da177e4 | 599 | { |
936c8bcd AD |
600 | char cmd[42]; |
601 | size_t len; | |
1da177e4 LT |
602 | int value; |
603 | u32 hci_result; | |
604 | ||
936c8bcd AD |
605 | len = min(count, sizeof(cmd) - 1); |
606 | if (copy_from_user(cmd, buf, len)) | |
607 | return -EFAULT; | |
608 | cmd[len] = '\0'; | |
609 | ||
610 | if (sscanf(cmd, " force_on : %i", &value) == 1 && | |
4be44fcd | 611 | value >= 0 && value <= 1) { |
1da177e4 LT |
612 | hci_write1(HCI_FAN, value, &hci_result); |
613 | if (hci_result != HCI_SUCCESS) | |
614 | return -EFAULT; | |
615 | else | |
616 | force_fan = value; | |
617 | } else { | |
618 | return -EINVAL; | |
619 | } | |
620 | ||
621 | return count; | |
622 | } | |
623 | ||
936c8bcd AD |
624 | static const struct file_operations fan_proc_fops = { |
625 | .owner = THIS_MODULE, | |
626 | .open = fan_proc_open, | |
627 | .read = seq_read, | |
628 | .llseek = seq_lseek, | |
629 | .release = single_release, | |
630 | .write = fan_proc_write, | |
631 | }; | |
632 | ||
633 | static int keys_proc_show(struct seq_file *m, void *v) | |
1da177e4 LT |
634 | { |
635 | u32 hci_result; | |
636 | u32 value; | |
637 | ||
638 | if (!key_event_valid) { | |
639 | hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); | |
640 | if (hci_result == HCI_SUCCESS) { | |
641 | key_event_valid = 1; | |
642 | last_key_event = value; | |
643 | } else if (hci_result == HCI_EMPTY) { | |
644 | /* better luck next time */ | |
645 | } else if (hci_result == HCI_NOT_SUPPORTED) { | |
646 | /* This is a workaround for an unresolved issue on | |
647 | * some machines where system events sporadically | |
648 | * become disabled. */ | |
649 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | |
650 | printk(MY_NOTICE "Re-enabled hotkeys\n"); | |
651 | } else { | |
652 | printk(MY_ERR "Error reading hotkey status\n"); | |
653 | goto end; | |
654 | } | |
655 | } | |
656 | ||
936c8bcd AD |
657 | seq_printf(m, "hotkey_ready: %d\n", key_event_valid); |
658 | seq_printf(m, "hotkey: 0x%04x\n", last_key_event); | |
659 | end: | |
660 | return 0; | |
661 | } | |
1da177e4 | 662 | |
936c8bcd AD |
663 | static int keys_proc_open(struct inode *inode, struct file *file) |
664 | { | |
665 | return single_open(file, keys_proc_show, NULL); | |
1da177e4 LT |
666 | } |
667 | ||
936c8bcd AD |
668 | static ssize_t keys_proc_write(struct file *file, const char __user *buf, |
669 | size_t count, loff_t *pos) | |
1da177e4 | 670 | { |
936c8bcd AD |
671 | char cmd[42]; |
672 | size_t len; | |
1da177e4 LT |
673 | int value; |
674 | ||
936c8bcd AD |
675 | len = min(count, sizeof(cmd) - 1); |
676 | if (copy_from_user(cmd, buf, len)) | |
677 | return -EFAULT; | |
678 | cmd[len] = '\0'; | |
679 | ||
680 | if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) { | |
1da177e4 LT |
681 | key_event_valid = 0; |
682 | } else { | |
683 | return -EINVAL; | |
684 | } | |
685 | ||
686 | return count; | |
687 | } | |
688 | ||
936c8bcd AD |
689 | static const struct file_operations keys_proc_fops = { |
690 | .owner = THIS_MODULE, | |
691 | .open = keys_proc_open, | |
692 | .read = seq_read, | |
693 | .llseek = seq_lseek, | |
694 | .release = single_release, | |
695 | .write = keys_proc_write, | |
696 | }; | |
697 | ||
698 | static int version_proc_show(struct seq_file *m, void *v) | |
1da177e4 | 699 | { |
936c8bcd AD |
700 | seq_printf(m, "driver: %s\n", TOSHIBA_ACPI_VERSION); |
701 | seq_printf(m, "proc_interface: %d\n", PROC_INTERFACE_VERSION); | |
702 | return 0; | |
1da177e4 LT |
703 | } |
704 | ||
936c8bcd AD |
705 | static int version_proc_open(struct inode *inode, struct file *file) |
706 | { | |
707 | return single_open(file, version_proc_show, PDE(inode)->data); | |
708 | } | |
709 | ||
710 | static const struct file_operations version_proc_fops = { | |
711 | .owner = THIS_MODULE, | |
712 | .open = version_proc_open, | |
713 | .read = seq_read, | |
714 | .llseek = seq_lseek, | |
715 | .release = single_release, | |
716 | }; | |
717 | ||
1da177e4 LT |
718 | /* proc and module init |
719 | */ | |
720 | ||
721 | #define PROC_TOSHIBA "toshiba" | |
722 | ||
4be44fcd | 723 | static acpi_status __init add_device(void) |
1da177e4 | 724 | { |
936c8bcd AD |
725 | proc_create("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir, &lcd_proc_fops); |
726 | proc_create("video", S_IRUGO | S_IWUSR, toshiba_proc_dir, &video_proc_fops); | |
727 | proc_create("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir, &fan_proc_fops); | |
728 | proc_create("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir, &keys_proc_fops); | |
729 | proc_create("version", S_IRUGO, toshiba_proc_dir, &version_proc_fops); | |
1da177e4 LT |
730 | |
731 | return AE_OK; | |
732 | } | |
733 | ||
b1d93de3 | 734 | static acpi_status remove_device(void) |
1da177e4 | 735 | { |
936c8bcd AD |
736 | remove_proc_entry("lcd", toshiba_proc_dir); |
737 | remove_proc_entry("video", toshiba_proc_dir); | |
738 | remove_proc_entry("fan", toshiba_proc_dir); | |
739 | remove_proc_entry("keys", toshiba_proc_dir); | |
740 | remove_proc_entry("version", toshiba_proc_dir); | |
1da177e4 LT |
741 | return AE_OK; |
742 | } | |
743 | ||
599a52d1 | 744 | static struct backlight_ops toshiba_backlight_data = { |
c9263557 HM |
745 | .get_brightness = get_lcd, |
746 | .update_status = set_lcd_status, | |
c9263557 HM |
747 | }; |
748 | ||
58b93995 | 749 | static struct key_entry *toshiba_acpi_get_entry_by_scancode(unsigned int code) |
6335e4d5 MG |
750 | { |
751 | struct key_entry *key; | |
752 | ||
753 | for (key = toshiba_acpi_keymap; key->type != KE_END; key++) | |
754 | if (code == key->code) | |
755 | return key; | |
756 | ||
757 | return NULL; | |
758 | } | |
759 | ||
58b93995 | 760 | static struct key_entry *toshiba_acpi_get_entry_by_keycode(unsigned int code) |
6335e4d5 MG |
761 | { |
762 | struct key_entry *key; | |
763 | ||
764 | for (key = toshiba_acpi_keymap; key->type != KE_END; key++) | |
765 | if (code == key->keycode && key->type == KE_KEY) | |
766 | return key; | |
767 | ||
768 | return NULL; | |
769 | } | |
770 | ||
58b93995 DT |
771 | static int toshiba_acpi_getkeycode(struct input_dev *dev, |
772 | unsigned int scancode, unsigned int *keycode) | |
6335e4d5 MG |
773 | { |
774 | struct key_entry *key = toshiba_acpi_get_entry_by_scancode(scancode); | |
775 | ||
776 | if (key && key->type == KE_KEY) { | |
777 | *keycode = key->keycode; | |
778 | return 0; | |
779 | } | |
780 | ||
781 | return -EINVAL; | |
782 | } | |
783 | ||
58b93995 DT |
784 | static int toshiba_acpi_setkeycode(struct input_dev *dev, |
785 | unsigned int scancode, unsigned int keycode) | |
6335e4d5 MG |
786 | { |
787 | struct key_entry *key; | |
58b93995 | 788 | unsigned int old_keycode; |
6335e4d5 MG |
789 | |
790 | key = toshiba_acpi_get_entry_by_scancode(scancode); | |
791 | if (key && key->type == KE_KEY) { | |
792 | old_keycode = key->keycode; | |
793 | key->keycode = keycode; | |
794 | set_bit(keycode, dev->keybit); | |
795 | if (!toshiba_acpi_get_entry_by_keycode(old_keycode)) | |
796 | clear_bit(old_keycode, dev->keybit); | |
797 | return 0; | |
798 | } | |
799 | ||
800 | return -EINVAL; | |
801 | } | |
802 | ||
803 | static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context) | |
804 | { | |
805 | u32 hci_result, value; | |
806 | struct key_entry *key; | |
807 | ||
808 | if (event != 0x80) | |
809 | return; | |
810 | do { | |
811 | hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); | |
812 | if (hci_result == HCI_SUCCESS) { | |
813 | if (value == 0x100) | |
814 | continue; | |
b466301b FP |
815 | /* act on key press; ignore key release */ |
816 | if (value & 0x80) | |
817 | continue; | |
818 | ||
819 | key = toshiba_acpi_get_entry_by_scancode | |
820 | (value); | |
821 | if (!key) { | |
822 | printk(MY_INFO "Unknown key %x\n", | |
823 | value); | |
824 | continue; | |
6335e4d5 | 825 | } |
b466301b FP |
826 | input_report_key(toshiba_acpi.hotkey_dev, |
827 | key->keycode, 1); | |
828 | input_sync(toshiba_acpi.hotkey_dev); | |
829 | input_report_key(toshiba_acpi.hotkey_dev, | |
830 | key->keycode, 0); | |
831 | input_sync(toshiba_acpi.hotkey_dev); | |
6335e4d5 MG |
832 | } else if (hci_result == HCI_NOT_SUPPORTED) { |
833 | /* This is a workaround for an unresolved issue on | |
834 | * some machines where system events sporadically | |
835 | * become disabled. */ | |
836 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | |
837 | printk(MY_NOTICE "Re-enabled hotkeys\n"); | |
838 | } | |
839 | } while (hci_result != HCI_EMPTY); | |
840 | } | |
841 | ||
842 | static int toshiba_acpi_setup_keyboard(char *device) | |
843 | { | |
844 | acpi_status status; | |
845 | acpi_handle handle; | |
846 | int result; | |
847 | const struct key_entry *key; | |
848 | ||
849 | status = acpi_get_handle(NULL, device, &handle); | |
850 | if (ACPI_FAILURE(status)) { | |
851 | printk(MY_INFO "Unable to get notification device\n"); | |
852 | return -ENODEV; | |
853 | } | |
854 | ||
855 | toshiba_acpi.handle = handle; | |
856 | ||
857 | status = acpi_evaluate_object(handle, "ENAB", NULL, NULL); | |
858 | if (ACPI_FAILURE(status)) { | |
859 | printk(MY_INFO "Unable to enable hotkeys\n"); | |
860 | return -ENODEV; | |
861 | } | |
862 | ||
863 | status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, | |
864 | toshiba_acpi_notify, NULL); | |
865 | if (ACPI_FAILURE(status)) { | |
866 | printk(MY_INFO "Unable to install hotkey notification\n"); | |
867 | return -ENODEV; | |
868 | } | |
869 | ||
870 | toshiba_acpi.hotkey_dev = input_allocate_device(); | |
871 | if (!toshiba_acpi.hotkey_dev) { | |
872 | printk(MY_INFO "Unable to register input device\n"); | |
873 | return -ENOMEM; | |
874 | } | |
875 | ||
876 | toshiba_acpi.hotkey_dev->name = "Toshiba input device"; | |
877 | toshiba_acpi.hotkey_dev->phys = device; | |
878 | toshiba_acpi.hotkey_dev->id.bustype = BUS_HOST; | |
879 | toshiba_acpi.hotkey_dev->getkeycode = toshiba_acpi_getkeycode; | |
880 | toshiba_acpi.hotkey_dev->setkeycode = toshiba_acpi_setkeycode; | |
881 | ||
882 | for (key = toshiba_acpi_keymap; key->type != KE_END; key++) { | |
883 | set_bit(EV_KEY, toshiba_acpi.hotkey_dev->evbit); | |
884 | set_bit(key->keycode, toshiba_acpi.hotkey_dev->keybit); | |
885 | } | |
886 | ||
887 | result = input_register_device(toshiba_acpi.hotkey_dev); | |
888 | if (result) { | |
889 | printk(MY_INFO "Unable to register input device\n"); | |
890 | return result; | |
891 | } | |
892 | ||
893 | return 0; | |
894 | } | |
895 | ||
b2b77b23 | 896 | static void toshiba_acpi_exit(void) |
c9263557 | 897 | { |
6335e4d5 MG |
898 | if (toshiba_acpi.hotkey_dev) |
899 | input_unregister_device(toshiba_acpi.hotkey_dev); | |
900 | ||
19d337df JB |
901 | if (toshiba_acpi.bt_rfk) { |
902 | rfkill_unregister(toshiba_acpi.bt_rfk); | |
903 | rfkill_destroy(toshiba_acpi.bt_rfk); | |
c41a40c5 | 904 | } |
905 | ||
c9263557 HM |
906 | if (toshiba_backlight_device) |
907 | backlight_device_unregister(toshiba_backlight_device); | |
908 | ||
909 | remove_device(); | |
910 | ||
911 | if (toshiba_proc_dir) | |
912 | remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); | |
913 | ||
6335e4d5 MG |
914 | acpi_remove_notify_handler(toshiba_acpi.handle, ACPI_DEVICE_NOTIFY, |
915 | toshiba_acpi_notify); | |
916 | ||
c41a40c5 | 917 | platform_device_unregister(toshiba_acpi.p_dev); |
918 | ||
c9263557 HM |
919 | return; |
920 | } | |
921 | ||
4be44fcd | 922 | static int __init toshiba_acpi_init(void) |
1da177e4 LT |
923 | { |
924 | acpi_status status = AE_OK; | |
925 | u32 hci_result; | |
c41a40c5 | 926 | bool bt_present; |
c41a40c5 | 927 | int ret = 0; |
a19a6ee6 | 928 | struct backlight_properties props; |
1da177e4 LT |
929 | |
930 | if (acpi_disabled) | |
931 | return -ENODEV; | |
fb9802fa | 932 | |
1da177e4 | 933 | /* simple device detection: look for HCI method */ |
6335e4d5 MG |
934 | if (is_valid_acpi_path(TOSH_INTERFACE_1 GHCI_METHOD)) { |
935 | method_hci = TOSH_INTERFACE_1 GHCI_METHOD; | |
936 | if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_1)) | |
937 | printk(MY_INFO "Unable to activate hotkeys\n"); | |
938 | } else if (is_valid_acpi_path(TOSH_INTERFACE_2 GHCI_METHOD)) { | |
939 | method_hci = TOSH_INTERFACE_2 GHCI_METHOD; | |
940 | if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_2)) | |
941 | printk(MY_INFO "Unable to activate hotkeys\n"); | |
942 | } else | |
1da177e4 LT |
943 | return -ENODEV; |
944 | ||
945 | printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", | |
4be44fcd | 946 | TOSHIBA_ACPI_VERSION); |
1da177e4 LT |
947 | printk(MY_INFO " HCI method: %s\n", method_hci); |
948 | ||
c41a40c5 | 949 | mutex_init(&toshiba_acpi.mutex); |
950 | ||
951 | toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi", | |
952 | -1, NULL, 0); | |
953 | if (IS_ERR(toshiba_acpi.p_dev)) { | |
954 | ret = PTR_ERR(toshiba_acpi.p_dev); | |
955 | printk(MY_ERR "unable to register platform device\n"); | |
956 | toshiba_acpi.p_dev = NULL; | |
957 | toshiba_acpi_exit(); | |
958 | return ret; | |
959 | } | |
960 | ||
1da177e4 LT |
961 | force_fan = 0; |
962 | key_event_valid = 0; | |
963 | ||
964 | /* enable event fifo */ | |
965 | hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); | |
966 | ||
967 | toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); | |
968 | if (!toshiba_proc_dir) { | |
c41a40c5 | 969 | toshiba_acpi_exit(); |
970 | return -ENODEV; | |
1da177e4 | 971 | } else { |
1da177e4 | 972 | status = add_device(); |
c41a40c5 | 973 | if (ACPI_FAILURE(status)) { |
974 | toshiba_acpi_exit(); | |
975 | return -ENODEV; | |
976 | } | |
1da177e4 LT |
977 | } |
978 | ||
a19a6ee6 | 979 | props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; |
c41a40c5 | 980 | toshiba_backlight_device = backlight_device_register("toshiba", |
a19a6ee6 MG |
981 | &toshiba_acpi.p_dev->dev, |
982 | NULL, | |
983 | &toshiba_backlight_data, | |
984 | &props); | |
c9263557 | 985 | if (IS_ERR(toshiba_backlight_device)) { |
c41a40c5 | 986 | ret = PTR_ERR(toshiba_backlight_device); |
1299342b | 987 | |
c9263557 HM |
988 | printk(KERN_ERR "Could not register toshiba backlight device\n"); |
989 | toshiba_backlight_device = NULL; | |
990 | toshiba_acpi_exit(); | |
1299342b | 991 | return ret; |
c9263557 | 992 | } |
1da177e4 | 993 | |
c41a40c5 | 994 | /* Register rfkill switch for Bluetooth */ |
995 | if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { | |
19d337df JB |
996 | toshiba_acpi.bt_rfk = rfkill_alloc(toshiba_acpi.bt_name, |
997 | &toshiba_acpi.p_dev->dev, | |
998 | RFKILL_TYPE_BLUETOOTH, | |
999 | &toshiba_rfk_ops, | |
1000 | &toshiba_acpi); | |
1001 | if (!toshiba_acpi.bt_rfk) { | |
c41a40c5 | 1002 | printk(MY_ERR "unable to allocate rfkill device\n"); |
1003 | toshiba_acpi_exit(); | |
1004 | return -ENOMEM; | |
1005 | } | |
1006 | ||
19d337df | 1007 | ret = rfkill_register(toshiba_acpi.bt_rfk); |
c41a40c5 | 1008 | if (ret) { |
1009 | printk(MY_ERR "unable to register rfkill device\n"); | |
19d337df | 1010 | rfkill_destroy(toshiba_acpi.bt_rfk); |
38aefbc5 FD |
1011 | toshiba_acpi_exit(); |
1012 | return ret; | |
1013 | } | |
c41a40c5 | 1014 | } |
1015 | ||
1016 | return 0; | |
1da177e4 LT |
1017 | } |
1018 | ||
1019 | module_init(toshiba_acpi_init); | |
1020 | module_exit(toshiba_acpi_exit); |