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