1b4f9fe12SLen Brown /* 2b4f9fe12SLen Brown * toshiba_acpi.c - Toshiba Laptop ACPI Extras 3b4f9fe12SLen Brown * 4b4f9fe12SLen Brown * 5b4f9fe12SLen Brown * Copyright (C) 2002-2004 John Belmonte 6b4f9fe12SLen Brown * Copyright (C) 2008 Philip Langdale 76c3f6e6cSPierre Ducroquet * Copyright (C) 2010 Pierre Ducroquet 8b4f9fe12SLen Brown * 9b4f9fe12SLen Brown * This program is free software; you can redistribute it and/or modify 10b4f9fe12SLen Brown * it under the terms of the GNU General Public License as published by 11b4f9fe12SLen Brown * the Free Software Foundation; either version 2 of the License, or 12b4f9fe12SLen Brown * (at your option) any later version. 13b4f9fe12SLen Brown * 14b4f9fe12SLen Brown * This program is distributed in the hope that it will be useful, 15b4f9fe12SLen Brown * but WITHOUT ANY WARRANTY; without even the implied warranty of 16b4f9fe12SLen Brown * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17b4f9fe12SLen Brown * GNU General Public License for more details. 18b4f9fe12SLen Brown * 19b4f9fe12SLen Brown * You should have received a copy of the GNU General Public License 20b4f9fe12SLen Brown * along with this program; if not, write to the Free Software 21b4f9fe12SLen Brown * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22b4f9fe12SLen Brown * 23b4f9fe12SLen Brown * 24b4f9fe12SLen Brown * The devolpment page for this driver is located at 25b4f9fe12SLen Brown * http://memebeam.org/toys/ToshibaAcpiDriver. 26b4f9fe12SLen Brown * 27b4f9fe12SLen Brown * Credits: 28b4f9fe12SLen Brown * Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse 29b4f9fe12SLen Brown * engineering the Windows drivers 30b4f9fe12SLen Brown * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 31b4f9fe12SLen Brown * Rob Miller - TV out and hotkeys help 32b4f9fe12SLen Brown * 33b4f9fe12SLen Brown * 34b4f9fe12SLen Brown * TODO 35b4f9fe12SLen Brown * 36b4f9fe12SLen Brown */ 37b4f9fe12SLen Brown 38b4f9fe12SLen Brown #define TOSHIBA_ACPI_VERSION "0.19" 39b4f9fe12SLen Brown #define PROC_INTERFACE_VERSION 1 40b4f9fe12SLen Brown 41b4f9fe12SLen Brown #include <linux/kernel.h> 42b4f9fe12SLen Brown #include <linux/module.h> 43b4f9fe12SLen Brown #include <linux/init.h> 44b4f9fe12SLen Brown #include <linux/types.h> 45b4f9fe12SLen Brown #include <linux/proc_fs.h> 46936c8bcdSAlexey Dobriyan #include <linux/seq_file.h> 47b4f9fe12SLen Brown #include <linux/backlight.h> 48b4f9fe12SLen Brown #include <linux/platform_device.h> 49b4f9fe12SLen Brown #include <linux/rfkill.h> 506335e4d5SMatthew Garrett #include <linux/input.h> 51*384a7cd9SDmitry Torokhov #include <linux/input/sparse-keymap.h> 526c3f6e6cSPierre Ducroquet #include <linux/leds.h> 535a0e3ad6STejun Heo #include <linux/slab.h> 54b4f9fe12SLen Brown 55b4f9fe12SLen Brown #include <asm/uaccess.h> 56b4f9fe12SLen Brown 57b4f9fe12SLen Brown #include <acpi/acpi_drivers.h> 58b4f9fe12SLen Brown 59b4f9fe12SLen Brown MODULE_AUTHOR("John Belmonte"); 60b4f9fe12SLen Brown MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver"); 61b4f9fe12SLen Brown MODULE_LICENSE("GPL"); 62b4f9fe12SLen Brown 63b4f9fe12SLen Brown #define MY_LOGPREFIX "toshiba_acpi: " 64b4f9fe12SLen Brown #define MY_ERR KERN_ERR MY_LOGPREFIX 65b4f9fe12SLen Brown #define MY_NOTICE KERN_NOTICE MY_LOGPREFIX 66b4f9fe12SLen Brown #define MY_INFO KERN_INFO MY_LOGPREFIX 67b4f9fe12SLen Brown 68b4f9fe12SLen Brown /* Toshiba ACPI method paths */ 69b4f9fe12SLen Brown #define METHOD_LCD_BRIGHTNESS "\\_SB_.PCI0.VGA_.LCD_._BCM" 706335e4d5SMatthew Garrett #define TOSH_INTERFACE_1 "\\_SB_.VALD" 716335e4d5SMatthew Garrett #define TOSH_INTERFACE_2 "\\_SB_.VALZ" 72b4f9fe12SLen Brown #define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX" 736335e4d5SMatthew Garrett #define GHCI_METHOD ".GHCI" 74b4f9fe12SLen Brown 75b4f9fe12SLen Brown /* Toshiba HCI interface definitions 76b4f9fe12SLen Brown * 77b4f9fe12SLen Brown * HCI is Toshiba's "Hardware Control Interface" which is supposed to 78b4f9fe12SLen Brown * be uniform across all their models. Ideally we would just call 79b4f9fe12SLen Brown * dedicated ACPI methods instead of using this primitive interface. 80b4f9fe12SLen Brown * However the ACPI methods seem to be incomplete in some areas (for 81b4f9fe12SLen Brown * example they allow setting, but not reading, the LCD brightness value), 82b4f9fe12SLen Brown * so this is still useful. 83b4f9fe12SLen Brown */ 84b4f9fe12SLen Brown 85b4f9fe12SLen Brown #define HCI_WORDS 6 86b4f9fe12SLen Brown 87b4f9fe12SLen Brown /* operations */ 88b4f9fe12SLen Brown #define HCI_SET 0xff00 89b4f9fe12SLen Brown #define HCI_GET 0xfe00 90b4f9fe12SLen Brown 91b4f9fe12SLen Brown /* return codes */ 92b4f9fe12SLen Brown #define HCI_SUCCESS 0x0000 93b4f9fe12SLen Brown #define HCI_FAILURE 0x1000 94b4f9fe12SLen Brown #define HCI_NOT_SUPPORTED 0x8000 95b4f9fe12SLen Brown #define HCI_EMPTY 0x8c00 96b4f9fe12SLen Brown 97b4f9fe12SLen Brown /* registers */ 98b4f9fe12SLen Brown #define HCI_FAN 0x0004 99b4f9fe12SLen Brown #define HCI_SYSTEM_EVENT 0x0016 100b4f9fe12SLen Brown #define HCI_VIDEO_OUT 0x001c 101b4f9fe12SLen Brown #define HCI_HOTKEY_EVENT 0x001e 102b4f9fe12SLen Brown #define HCI_LCD_BRIGHTNESS 0x002a 103b4f9fe12SLen Brown #define HCI_WIRELESS 0x0056 104b4f9fe12SLen Brown 105b4f9fe12SLen Brown /* field definitions */ 106b4f9fe12SLen Brown #define HCI_LCD_BRIGHTNESS_BITS 3 107b4f9fe12SLen Brown #define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS) 108b4f9fe12SLen Brown #define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS) 109b4f9fe12SLen Brown #define HCI_VIDEO_OUT_LCD 0x1 110b4f9fe12SLen Brown #define HCI_VIDEO_OUT_CRT 0x2 111b4f9fe12SLen Brown #define HCI_VIDEO_OUT_TV 0x4 112b4f9fe12SLen Brown #define HCI_WIRELESS_KILL_SWITCH 0x01 113b4f9fe12SLen Brown #define HCI_WIRELESS_BT_PRESENT 0x0f 114b4f9fe12SLen Brown #define HCI_WIRELESS_BT_ATTACH 0x40 115b4f9fe12SLen Brown #define HCI_WIRELESS_BT_POWER 0x80 116b4f9fe12SLen Brown 117b4f9fe12SLen Brown static const struct acpi_device_id toshiba_device_ids[] = { 118b4f9fe12SLen Brown {"TOS6200", 0}, 119b4f9fe12SLen Brown {"TOS6208", 0}, 120b4f9fe12SLen Brown {"TOS1900", 0}, 121b4f9fe12SLen Brown {"", 0}, 122b4f9fe12SLen Brown }; 123b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, toshiba_device_ids); 124b4f9fe12SLen Brown 125*384a7cd9SDmitry Torokhov static const struct key_entry toshiba_acpi_keymap[] __initconst = { 126*384a7cd9SDmitry Torokhov { KE_KEY, 0x101, { KEY_MUTE } }, 127*384a7cd9SDmitry Torokhov { KE_KEY, 0x102, { KEY_ZOOMOUT } }, 128*384a7cd9SDmitry Torokhov { KE_KEY, 0x103, { KEY_ZOOMIN } }, 129*384a7cd9SDmitry Torokhov { KE_KEY, 0x13b, { KEY_COFFEE } }, 130*384a7cd9SDmitry Torokhov { KE_KEY, 0x13c, { KEY_BATTERY } }, 131*384a7cd9SDmitry Torokhov { KE_KEY, 0x13d, { KEY_SLEEP } }, 132*384a7cd9SDmitry Torokhov { KE_KEY, 0x13e, { KEY_SUSPEND } }, 133*384a7cd9SDmitry Torokhov { KE_KEY, 0x13f, { KEY_SWITCHVIDEOMODE } }, 134*384a7cd9SDmitry Torokhov { KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } }, 135*384a7cd9SDmitry Torokhov { KE_KEY, 0x141, { KEY_BRIGHTNESSUP } }, 136*384a7cd9SDmitry Torokhov { KE_KEY, 0x142, { KEY_WLAN } }, 137*384a7cd9SDmitry Torokhov { KE_KEY, 0x143, { KEY_PROG1 } }, 138*384a7cd9SDmitry Torokhov { KE_KEY, 0xb05, { KEY_PROG2 } }, 139*384a7cd9SDmitry Torokhov { KE_KEY, 0xb06, { KEY_WWW } }, 140*384a7cd9SDmitry Torokhov { KE_KEY, 0xb07, { KEY_MAIL } }, 141*384a7cd9SDmitry Torokhov { KE_KEY, 0xb30, { KEY_STOP } }, 142*384a7cd9SDmitry Torokhov { KE_KEY, 0xb31, { KEY_PREVIOUSSONG } }, 143*384a7cd9SDmitry Torokhov { KE_KEY, 0xb32, { KEY_NEXTSONG } }, 144*384a7cd9SDmitry Torokhov { KE_KEY, 0xb33, { KEY_PLAYPAUSE } }, 145*384a7cd9SDmitry Torokhov { KE_KEY, 0xb5a, { KEY_MEDIA } }, 146*384a7cd9SDmitry Torokhov { KE_END, 0 }, 1476335e4d5SMatthew Garrett }; 1486335e4d5SMatthew Garrett 149b4f9fe12SLen Brown /* utility 150b4f9fe12SLen Brown */ 151b4f9fe12SLen Brown 152b4f9fe12SLen Brown static __inline__ void _set_bit(u32 * word, u32 mask, int value) 153b4f9fe12SLen Brown { 154b4f9fe12SLen Brown *word = (*word & ~mask) | (mask * value); 155b4f9fe12SLen Brown } 156b4f9fe12SLen Brown 157b4f9fe12SLen Brown /* acpi interface wrappers 158b4f9fe12SLen Brown */ 159b4f9fe12SLen Brown 160b4f9fe12SLen Brown static int is_valid_acpi_path(const char *methodName) 161b4f9fe12SLen Brown { 162b4f9fe12SLen Brown acpi_handle handle; 163b4f9fe12SLen Brown acpi_status status; 164b4f9fe12SLen Brown 165b4f9fe12SLen Brown status = acpi_get_handle(NULL, (char *)methodName, &handle); 166b4f9fe12SLen Brown return !ACPI_FAILURE(status); 167b4f9fe12SLen Brown } 168b4f9fe12SLen Brown 169b4f9fe12SLen Brown static int write_acpi_int(const char *methodName, int val) 170b4f9fe12SLen Brown { 171b4f9fe12SLen Brown struct acpi_object_list params; 172b4f9fe12SLen Brown union acpi_object in_objs[1]; 173b4f9fe12SLen Brown acpi_status status; 174b4f9fe12SLen Brown 175b4f9fe12SLen Brown params.count = ARRAY_SIZE(in_objs); 176b4f9fe12SLen Brown params.pointer = in_objs; 177b4f9fe12SLen Brown in_objs[0].type = ACPI_TYPE_INTEGER; 178b4f9fe12SLen Brown in_objs[0].integer.value = val; 179b4f9fe12SLen Brown 180b4f9fe12SLen Brown status = acpi_evaluate_object(NULL, (char *)methodName, ¶ms, NULL); 181b4f9fe12SLen Brown return (status == AE_OK); 182b4f9fe12SLen Brown } 183b4f9fe12SLen Brown 184b4f9fe12SLen Brown #if 0 185b4f9fe12SLen Brown static int read_acpi_int(const char *methodName, int *pVal) 186b4f9fe12SLen Brown { 187b4f9fe12SLen Brown struct acpi_buffer results; 188b4f9fe12SLen Brown union acpi_object out_objs[1]; 189b4f9fe12SLen Brown acpi_status status; 190b4f9fe12SLen Brown 191b4f9fe12SLen Brown results.length = sizeof(out_objs); 192b4f9fe12SLen Brown results.pointer = out_objs; 193b4f9fe12SLen Brown 194b4f9fe12SLen Brown status = acpi_evaluate_object(0, (char *)methodName, 0, &results); 195b4f9fe12SLen Brown *pVal = out_objs[0].integer.value; 196b4f9fe12SLen Brown 197b4f9fe12SLen Brown return (status == AE_OK) && (out_objs[0].type == ACPI_TYPE_INTEGER); 198b4f9fe12SLen Brown } 199b4f9fe12SLen Brown #endif 200b4f9fe12SLen Brown 201b4f9fe12SLen Brown static const char *method_hci /*= 0*/ ; 202b4f9fe12SLen Brown 203b4f9fe12SLen Brown /* Perform a raw HCI call. Here we don't care about input or output buffer 204b4f9fe12SLen Brown * format. 205b4f9fe12SLen Brown */ 206b4f9fe12SLen Brown static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS]) 207b4f9fe12SLen Brown { 208b4f9fe12SLen Brown struct acpi_object_list params; 209b4f9fe12SLen Brown union acpi_object in_objs[HCI_WORDS]; 210b4f9fe12SLen Brown struct acpi_buffer results; 211b4f9fe12SLen Brown union acpi_object out_objs[HCI_WORDS + 1]; 212b4f9fe12SLen Brown acpi_status status; 213b4f9fe12SLen Brown int i; 214b4f9fe12SLen Brown 215b4f9fe12SLen Brown params.count = HCI_WORDS; 216b4f9fe12SLen Brown params.pointer = in_objs; 217b4f9fe12SLen Brown for (i = 0; i < HCI_WORDS; ++i) { 218b4f9fe12SLen Brown in_objs[i].type = ACPI_TYPE_INTEGER; 219b4f9fe12SLen Brown in_objs[i].integer.value = in[i]; 220b4f9fe12SLen Brown } 221b4f9fe12SLen Brown 222b4f9fe12SLen Brown results.length = sizeof(out_objs); 223b4f9fe12SLen Brown results.pointer = out_objs; 224b4f9fe12SLen Brown 225b4f9fe12SLen Brown status = acpi_evaluate_object(NULL, (char *)method_hci, ¶ms, 226b4f9fe12SLen Brown &results); 227b4f9fe12SLen Brown if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) { 228b4f9fe12SLen Brown for (i = 0; i < out_objs->package.count; ++i) { 229b4f9fe12SLen Brown out[i] = out_objs->package.elements[i].integer.value; 230b4f9fe12SLen Brown } 231b4f9fe12SLen Brown } 232b4f9fe12SLen Brown 233b4f9fe12SLen Brown return status; 234b4f9fe12SLen Brown } 235b4f9fe12SLen Brown 236b4f9fe12SLen Brown /* common hci tasks (get or set one or two value) 237b4f9fe12SLen Brown * 238b4f9fe12SLen Brown * In addition to the ACPI status, the HCI system returns a result which 239b4f9fe12SLen Brown * may be useful (such as "not supported"). 240b4f9fe12SLen Brown */ 241b4f9fe12SLen Brown 242b4f9fe12SLen Brown static acpi_status hci_write1(u32 reg, u32 in1, u32 * result) 243b4f9fe12SLen Brown { 244b4f9fe12SLen Brown u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; 245b4f9fe12SLen Brown u32 out[HCI_WORDS]; 246b4f9fe12SLen Brown acpi_status status = hci_raw(in, out); 247b4f9fe12SLen Brown *result = (status == AE_OK) ? out[0] : HCI_FAILURE; 248b4f9fe12SLen Brown return status; 249b4f9fe12SLen Brown } 250b4f9fe12SLen Brown 251b4f9fe12SLen Brown static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) 252b4f9fe12SLen Brown { 253b4f9fe12SLen Brown u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; 254b4f9fe12SLen Brown u32 out[HCI_WORDS]; 255b4f9fe12SLen Brown acpi_status status = hci_raw(in, out); 256b4f9fe12SLen Brown *out1 = out[2]; 257b4f9fe12SLen Brown *result = (status == AE_OK) ? out[0] : HCI_FAILURE; 258b4f9fe12SLen Brown return status; 259b4f9fe12SLen Brown } 260b4f9fe12SLen Brown 261b4f9fe12SLen Brown static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32 *result) 262b4f9fe12SLen Brown { 263b4f9fe12SLen Brown u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; 264b4f9fe12SLen Brown u32 out[HCI_WORDS]; 265b4f9fe12SLen Brown acpi_status status = hci_raw(in, out); 266b4f9fe12SLen Brown *result = (status == AE_OK) ? out[0] : HCI_FAILURE; 267b4f9fe12SLen Brown return status; 268b4f9fe12SLen Brown } 269b4f9fe12SLen Brown 270b4f9fe12SLen Brown static acpi_status hci_read2(u32 reg, u32 *out1, u32 *out2, u32 *result) 271b4f9fe12SLen Brown { 272b4f9fe12SLen Brown u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; 273b4f9fe12SLen Brown u32 out[HCI_WORDS]; 274b4f9fe12SLen Brown acpi_status status = hci_raw(in, out); 275b4f9fe12SLen Brown *out1 = out[2]; 276b4f9fe12SLen Brown *out2 = out[3]; 277b4f9fe12SLen Brown *result = (status == AE_OK) ? out[0] : HCI_FAILURE; 278b4f9fe12SLen Brown return status; 279b4f9fe12SLen Brown } 280b4f9fe12SLen Brown 281b4f9fe12SLen Brown struct toshiba_acpi_dev { 282b4f9fe12SLen Brown struct platform_device *p_dev; 28319d337dfSJohannes Berg struct rfkill *bt_rfk; 2846335e4d5SMatthew Garrett struct input_dev *hotkey_dev; 2856c3f6e6cSPierre Ducroquet int illumination_installed; 2866335e4d5SMatthew Garrett acpi_handle handle; 287b4f9fe12SLen Brown 288b4f9fe12SLen Brown const char *bt_name; 289b4f9fe12SLen Brown 290b4f9fe12SLen Brown struct mutex mutex; 291b4f9fe12SLen Brown }; 292b4f9fe12SLen Brown 2936c3f6e6cSPierre Ducroquet /* Illumination support */ 2946c3f6e6cSPierre Ducroquet static int toshiba_illumination_available(void) 2956c3f6e6cSPierre Ducroquet { 2966c3f6e6cSPierre Ducroquet u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; 2976c3f6e6cSPierre Ducroquet u32 out[HCI_WORDS]; 2986c3f6e6cSPierre Ducroquet acpi_status status; 2996c3f6e6cSPierre Ducroquet 3006c3f6e6cSPierre Ducroquet in[0] = 0xf100; 3016c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3026c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3036c3f6e6cSPierre Ducroquet printk(MY_INFO "Illumination device not available\n"); 3046c3f6e6cSPierre Ducroquet return 0; 3056c3f6e6cSPierre Ducroquet } 3066c3f6e6cSPierre Ducroquet in[0] = 0xf400; 3076c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3086c3f6e6cSPierre Ducroquet return 1; 3096c3f6e6cSPierre Ducroquet } 3106c3f6e6cSPierre Ducroquet 3116c3f6e6cSPierre Ducroquet static void toshiba_illumination_set(struct led_classdev *cdev, 3126c3f6e6cSPierre Ducroquet enum led_brightness brightness) 3136c3f6e6cSPierre Ducroquet { 3146c3f6e6cSPierre Ducroquet u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; 3156c3f6e6cSPierre Ducroquet u32 out[HCI_WORDS]; 3166c3f6e6cSPierre Ducroquet acpi_status status; 3176c3f6e6cSPierre Ducroquet 3186c3f6e6cSPierre Ducroquet /* First request : initialize communication. */ 3196c3f6e6cSPierre Ducroquet in[0] = 0xf100; 3206c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3216c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3226c3f6e6cSPierre Ducroquet printk(MY_INFO "Illumination device not available\n"); 3236c3f6e6cSPierre Ducroquet return; 3246c3f6e6cSPierre Ducroquet } 3256c3f6e6cSPierre Ducroquet 3266c3f6e6cSPierre Ducroquet if (brightness) { 3276c3f6e6cSPierre Ducroquet /* Switch the illumination on */ 3286c3f6e6cSPierre Ducroquet in[0] = 0xf400; 3296c3f6e6cSPierre Ducroquet in[1] = 0x14e; 3306c3f6e6cSPierre Ducroquet in[2] = 1; 3316c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3326c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3336c3f6e6cSPierre Ducroquet printk(MY_INFO "ACPI call for illumination failed.\n"); 3346c3f6e6cSPierre Ducroquet return; 3356c3f6e6cSPierre Ducroquet } 3366c3f6e6cSPierre Ducroquet } else { 3376c3f6e6cSPierre Ducroquet /* Switch the illumination off */ 3386c3f6e6cSPierre Ducroquet in[0] = 0xf400; 3396c3f6e6cSPierre Ducroquet in[1] = 0x14e; 3406c3f6e6cSPierre Ducroquet in[2] = 0; 3416c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3426c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3436c3f6e6cSPierre Ducroquet printk(MY_INFO "ACPI call for illumination failed.\n"); 3446c3f6e6cSPierre Ducroquet return; 3456c3f6e6cSPierre Ducroquet } 3466c3f6e6cSPierre Ducroquet } 3476c3f6e6cSPierre Ducroquet 3486c3f6e6cSPierre Ducroquet /* Last request : close communication. */ 3496c3f6e6cSPierre Ducroquet in[0] = 0xf200; 3506c3f6e6cSPierre Ducroquet in[1] = 0; 3516c3f6e6cSPierre Ducroquet in[2] = 0; 3526c3f6e6cSPierre Ducroquet hci_raw(in, out); 3536c3f6e6cSPierre Ducroquet } 3546c3f6e6cSPierre Ducroquet 3556c3f6e6cSPierre Ducroquet static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) 3566c3f6e6cSPierre Ducroquet { 3576c3f6e6cSPierre Ducroquet u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; 3586c3f6e6cSPierre Ducroquet u32 out[HCI_WORDS]; 3596c3f6e6cSPierre Ducroquet acpi_status status; 3606c3f6e6cSPierre Ducroquet enum led_brightness result; 3616c3f6e6cSPierre Ducroquet 3626c3f6e6cSPierre Ducroquet /* First request : initialize communication. */ 3636c3f6e6cSPierre Ducroquet in[0] = 0xf100; 3646c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3656c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3666c3f6e6cSPierre Ducroquet printk(MY_INFO "Illumination device not available\n"); 3676c3f6e6cSPierre Ducroquet return LED_OFF; 3686c3f6e6cSPierre Ducroquet } 3696c3f6e6cSPierre Ducroquet 3706c3f6e6cSPierre Ducroquet /* Check the illumination */ 3716c3f6e6cSPierre Ducroquet in[0] = 0xf300; 3726c3f6e6cSPierre Ducroquet in[1] = 0x14e; 3736c3f6e6cSPierre Ducroquet status = hci_raw(in, out); 3746c3f6e6cSPierre Ducroquet if (ACPI_FAILURE(status)) { 3756c3f6e6cSPierre Ducroquet printk(MY_INFO "ACPI call for illumination failed.\n"); 3766c3f6e6cSPierre Ducroquet return LED_OFF; 3776c3f6e6cSPierre Ducroquet } 3786c3f6e6cSPierre Ducroquet 3796c3f6e6cSPierre Ducroquet result = out[2] ? LED_FULL : LED_OFF; 3806c3f6e6cSPierre Ducroquet 3816c3f6e6cSPierre Ducroquet /* Last request : close communication. */ 3826c3f6e6cSPierre Ducroquet in[0] = 0xf200; 3836c3f6e6cSPierre Ducroquet in[1] = 0; 3846c3f6e6cSPierre Ducroquet in[2] = 0; 3856c3f6e6cSPierre Ducroquet hci_raw(in, out); 3866c3f6e6cSPierre Ducroquet 3876c3f6e6cSPierre Ducroquet return result; 3886c3f6e6cSPierre Ducroquet } 3896c3f6e6cSPierre Ducroquet 3906c3f6e6cSPierre Ducroquet static struct led_classdev toshiba_led = { 3916c3f6e6cSPierre Ducroquet .name = "toshiba::illumination", 3926c3f6e6cSPierre Ducroquet .max_brightness = 1, 3936c3f6e6cSPierre Ducroquet .brightness_set = toshiba_illumination_set, 3946c3f6e6cSPierre Ducroquet .brightness_get = toshiba_illumination_get, 3956c3f6e6cSPierre Ducroquet }; 3966c3f6e6cSPierre Ducroquet 397b4f9fe12SLen Brown static struct toshiba_acpi_dev toshiba_acpi = { 398b4f9fe12SLen Brown .bt_name = "Toshiba Bluetooth", 399b4f9fe12SLen Brown }; 400b4f9fe12SLen Brown 401b4f9fe12SLen Brown /* Bluetooth rfkill handlers */ 402b4f9fe12SLen Brown 403b4f9fe12SLen Brown static u32 hci_get_bt_present(bool *present) 404b4f9fe12SLen Brown { 405b4f9fe12SLen Brown u32 hci_result; 406b4f9fe12SLen Brown u32 value, value2; 407b4f9fe12SLen Brown 408b4f9fe12SLen Brown value = 0; 409b4f9fe12SLen Brown value2 = 0; 410b4f9fe12SLen Brown hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); 411b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) 412b4f9fe12SLen Brown *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; 413b4f9fe12SLen Brown 414b4f9fe12SLen Brown return hci_result; 415b4f9fe12SLen Brown } 416b4f9fe12SLen Brown 417b4f9fe12SLen Brown static u32 hci_get_radio_state(bool *radio_state) 418b4f9fe12SLen Brown { 419b4f9fe12SLen Brown u32 hci_result; 420b4f9fe12SLen Brown u32 value, value2; 421b4f9fe12SLen Brown 422b4f9fe12SLen Brown value = 0; 423b4f9fe12SLen Brown value2 = 0x0001; 424b4f9fe12SLen Brown hci_read2(HCI_WIRELESS, &value, &value2, &hci_result); 425b4f9fe12SLen Brown 426b4f9fe12SLen Brown *radio_state = value & HCI_WIRELESS_KILL_SWITCH; 427b4f9fe12SLen Brown return hci_result; 428b4f9fe12SLen Brown } 429b4f9fe12SLen Brown 43019d337dfSJohannes Berg static int bt_rfkill_set_block(void *data, bool blocked) 431b4f9fe12SLen Brown { 43219d337dfSJohannes Berg struct toshiba_acpi_dev *dev = data; 433b4f9fe12SLen Brown u32 result1, result2; 434b4f9fe12SLen Brown u32 value; 43519d337dfSJohannes Berg int err; 436b4f9fe12SLen Brown bool radio_state; 437b4f9fe12SLen Brown 43819d337dfSJohannes Berg value = (blocked == false); 439b4f9fe12SLen Brown 440b4f9fe12SLen Brown mutex_lock(&dev->mutex); 44119d337dfSJohannes Berg if (hci_get_radio_state(&radio_state) != HCI_SUCCESS) { 44219d337dfSJohannes Berg err = -EBUSY; 44319d337dfSJohannes Berg goto out; 444b4f9fe12SLen Brown } 445b4f9fe12SLen Brown 44619d337dfSJohannes Berg if (!radio_state) { 44719d337dfSJohannes Berg err = 0; 44819d337dfSJohannes Berg goto out; 44919d337dfSJohannes Berg } 45019d337dfSJohannes Berg 45119d337dfSJohannes Berg hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1); 45219d337dfSJohannes Berg hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2); 45319d337dfSJohannes Berg 45419d337dfSJohannes Berg if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) 45519d337dfSJohannes Berg err = -EBUSY; 45619d337dfSJohannes Berg else 45719d337dfSJohannes Berg err = 0; 45819d337dfSJohannes Berg out: 45919d337dfSJohannes Berg mutex_unlock(&dev->mutex); 46019d337dfSJohannes Berg return err; 46119d337dfSJohannes Berg } 46219d337dfSJohannes Berg 46319d337dfSJohannes Berg static void bt_rfkill_poll(struct rfkill *rfkill, void *data) 464b4f9fe12SLen Brown { 465b4f9fe12SLen Brown bool new_rfk_state; 466b4f9fe12SLen Brown bool value; 467b4f9fe12SLen Brown u32 hci_result; 46819d337dfSJohannes Berg struct toshiba_acpi_dev *dev = data; 46919d337dfSJohannes Berg 47019d337dfSJohannes Berg mutex_lock(&dev->mutex); 471b4f9fe12SLen Brown 472b4f9fe12SLen Brown hci_result = hci_get_radio_state(&value); 47319d337dfSJohannes Berg if (hci_result != HCI_SUCCESS) { 47419d337dfSJohannes Berg /* Can't do anything useful */ 47519d337dfSJohannes Berg mutex_unlock(&dev->mutex); 47682e7784fSJiri Slaby return; 47719d337dfSJohannes Berg } 478b4f9fe12SLen Brown 479b4f9fe12SLen Brown new_rfk_state = value; 480b4f9fe12SLen Brown 481b4f9fe12SLen Brown mutex_unlock(&dev->mutex); 482b4f9fe12SLen Brown 48319d337dfSJohannes Berg if (rfkill_set_hw_state(rfkill, !new_rfk_state)) 48419d337dfSJohannes Berg bt_rfkill_set_block(data, true); 485b4f9fe12SLen Brown } 48619d337dfSJohannes Berg 48719d337dfSJohannes Berg static const struct rfkill_ops toshiba_rfk_ops = { 48819d337dfSJohannes Berg .set_block = bt_rfkill_set_block, 48919d337dfSJohannes Berg .poll = bt_rfkill_poll, 49019d337dfSJohannes Berg }; 491b4f9fe12SLen Brown 492b4f9fe12SLen Brown static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; 493b4f9fe12SLen Brown static struct backlight_device *toshiba_backlight_device; 494b4f9fe12SLen Brown static int force_fan; 495b4f9fe12SLen Brown static int last_key_event; 496b4f9fe12SLen Brown static int key_event_valid; 497b4f9fe12SLen Brown 498b4f9fe12SLen Brown static int get_lcd(struct backlight_device *bd) 499b4f9fe12SLen Brown { 500b4f9fe12SLen Brown u32 hci_result; 501b4f9fe12SLen Brown u32 value; 502b4f9fe12SLen Brown 503b4f9fe12SLen Brown hci_read1(HCI_LCD_BRIGHTNESS, &value, &hci_result); 504b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) { 505b4f9fe12SLen Brown return (value >> HCI_LCD_BRIGHTNESS_SHIFT); 506b4f9fe12SLen Brown } else 507b4f9fe12SLen Brown return -EFAULT; 508b4f9fe12SLen Brown } 509b4f9fe12SLen Brown 510936c8bcdSAlexey Dobriyan static int lcd_proc_show(struct seq_file *m, void *v) 511b4f9fe12SLen Brown { 512b4f9fe12SLen Brown int value = get_lcd(NULL); 513b4f9fe12SLen Brown 514b4f9fe12SLen Brown if (value >= 0) { 515936c8bcdSAlexey Dobriyan seq_printf(m, "brightness: %d\n", value); 516936c8bcdSAlexey Dobriyan seq_printf(m, "brightness_levels: %d\n", 517b4f9fe12SLen Brown HCI_LCD_BRIGHTNESS_LEVELS); 518b4f9fe12SLen Brown } else { 519b4f9fe12SLen Brown printk(MY_ERR "Error reading LCD brightness\n"); 520b4f9fe12SLen Brown } 521b4f9fe12SLen Brown 522936c8bcdSAlexey Dobriyan return 0; 523936c8bcdSAlexey Dobriyan } 524936c8bcdSAlexey Dobriyan 525936c8bcdSAlexey Dobriyan static int lcd_proc_open(struct inode *inode, struct file *file) 526936c8bcdSAlexey Dobriyan { 527936c8bcdSAlexey Dobriyan return single_open(file, lcd_proc_show, NULL); 528b4f9fe12SLen Brown } 529b4f9fe12SLen Brown 530b4f9fe12SLen Brown static int set_lcd(int value) 531b4f9fe12SLen Brown { 532b4f9fe12SLen Brown u32 hci_result; 533b4f9fe12SLen Brown 534b4f9fe12SLen Brown value = value << HCI_LCD_BRIGHTNESS_SHIFT; 535b4f9fe12SLen Brown hci_write1(HCI_LCD_BRIGHTNESS, value, &hci_result); 536b4f9fe12SLen Brown if (hci_result != HCI_SUCCESS) 537b4f9fe12SLen Brown return -EFAULT; 538b4f9fe12SLen Brown 539b4f9fe12SLen Brown return 0; 540b4f9fe12SLen Brown } 541b4f9fe12SLen Brown 542b4f9fe12SLen Brown static int set_lcd_status(struct backlight_device *bd) 543b4f9fe12SLen Brown { 544b4f9fe12SLen Brown return set_lcd(bd->props.brightness); 545b4f9fe12SLen Brown } 546b4f9fe12SLen Brown 547936c8bcdSAlexey Dobriyan static ssize_t lcd_proc_write(struct file *file, const char __user *buf, 548936c8bcdSAlexey Dobriyan size_t count, loff_t *pos) 549b4f9fe12SLen Brown { 550936c8bcdSAlexey Dobriyan char cmd[42]; 551936c8bcdSAlexey Dobriyan size_t len; 552b4f9fe12SLen Brown int value; 553b4f9fe12SLen Brown int ret; 554b4f9fe12SLen Brown 555936c8bcdSAlexey Dobriyan len = min(count, sizeof(cmd) - 1); 556936c8bcdSAlexey Dobriyan if (copy_from_user(cmd, buf, len)) 557936c8bcdSAlexey Dobriyan return -EFAULT; 558936c8bcdSAlexey Dobriyan cmd[len] = '\0'; 559936c8bcdSAlexey Dobriyan 560936c8bcdSAlexey Dobriyan if (sscanf(cmd, " brightness : %i", &value) == 1 && 561b4f9fe12SLen Brown value >= 0 && value < HCI_LCD_BRIGHTNESS_LEVELS) { 562b4f9fe12SLen Brown ret = set_lcd(value); 563b4f9fe12SLen Brown if (ret == 0) 564b4f9fe12SLen Brown ret = count; 565b4f9fe12SLen Brown } else { 566b4f9fe12SLen Brown ret = -EINVAL; 567b4f9fe12SLen Brown } 568b4f9fe12SLen Brown return ret; 569b4f9fe12SLen Brown } 570b4f9fe12SLen Brown 571936c8bcdSAlexey Dobriyan static const struct file_operations lcd_proc_fops = { 572936c8bcdSAlexey Dobriyan .owner = THIS_MODULE, 573936c8bcdSAlexey Dobriyan .open = lcd_proc_open, 574936c8bcdSAlexey Dobriyan .read = seq_read, 575936c8bcdSAlexey Dobriyan .llseek = seq_lseek, 576936c8bcdSAlexey Dobriyan .release = single_release, 577936c8bcdSAlexey Dobriyan .write = lcd_proc_write, 578936c8bcdSAlexey Dobriyan }; 579936c8bcdSAlexey Dobriyan 580936c8bcdSAlexey Dobriyan static int video_proc_show(struct seq_file *m, void *v) 581b4f9fe12SLen Brown { 582b4f9fe12SLen Brown u32 hci_result; 583b4f9fe12SLen Brown u32 value; 584b4f9fe12SLen Brown 585b4f9fe12SLen Brown hci_read1(HCI_VIDEO_OUT, &value, &hci_result); 586b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) { 587b4f9fe12SLen Brown int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0; 588b4f9fe12SLen Brown int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0; 589b4f9fe12SLen Brown int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0; 590936c8bcdSAlexey Dobriyan seq_printf(m, "lcd_out: %d\n", is_lcd); 591936c8bcdSAlexey Dobriyan seq_printf(m, "crt_out: %d\n", is_crt); 592936c8bcdSAlexey Dobriyan seq_printf(m, "tv_out: %d\n", is_tv); 593b4f9fe12SLen Brown } else { 594b4f9fe12SLen Brown printk(MY_ERR "Error reading video out status\n"); 595b4f9fe12SLen Brown } 596b4f9fe12SLen Brown 597936c8bcdSAlexey Dobriyan return 0; 598b4f9fe12SLen Brown } 599b4f9fe12SLen Brown 600936c8bcdSAlexey Dobriyan static int video_proc_open(struct inode *inode, struct file *file) 601b4f9fe12SLen Brown { 602936c8bcdSAlexey Dobriyan return single_open(file, video_proc_show, NULL); 603936c8bcdSAlexey Dobriyan } 604936c8bcdSAlexey Dobriyan 605936c8bcdSAlexey Dobriyan static ssize_t video_proc_write(struct file *file, const char __user *buf, 606936c8bcdSAlexey Dobriyan size_t count, loff_t *pos) 607936c8bcdSAlexey Dobriyan { 608936c8bcdSAlexey Dobriyan char *cmd, *buffer; 609b4f9fe12SLen Brown int value; 610b4f9fe12SLen Brown int remain = count; 611b4f9fe12SLen Brown int lcd_out = -1; 612b4f9fe12SLen Brown int crt_out = -1; 613b4f9fe12SLen Brown int tv_out = -1; 614b4f9fe12SLen Brown u32 hci_result; 615b4f9fe12SLen Brown u32 video_out; 616b4f9fe12SLen Brown 617936c8bcdSAlexey Dobriyan cmd = kmalloc(count + 1, GFP_KERNEL); 618936c8bcdSAlexey Dobriyan if (!cmd) 619936c8bcdSAlexey Dobriyan return -ENOMEM; 620936c8bcdSAlexey Dobriyan if (copy_from_user(cmd, buf, count)) { 621936c8bcdSAlexey Dobriyan kfree(cmd); 622936c8bcdSAlexey Dobriyan return -EFAULT; 623936c8bcdSAlexey Dobriyan } 624936c8bcdSAlexey Dobriyan cmd[count] = '\0'; 625936c8bcdSAlexey Dobriyan 626936c8bcdSAlexey Dobriyan buffer = cmd; 627936c8bcdSAlexey Dobriyan 628b4f9fe12SLen Brown /* scan expression. Multiple expressions may be delimited with ; 629b4f9fe12SLen Brown * 630b4f9fe12SLen Brown * NOTE: to keep scanning simple, invalid fields are ignored 631b4f9fe12SLen Brown */ 632b4f9fe12SLen Brown while (remain) { 633b4f9fe12SLen Brown if (sscanf(buffer, " lcd_out : %i", &value) == 1) 634b4f9fe12SLen Brown lcd_out = value & 1; 635b4f9fe12SLen Brown else if (sscanf(buffer, " crt_out : %i", &value) == 1) 636b4f9fe12SLen Brown crt_out = value & 1; 637b4f9fe12SLen Brown else if (sscanf(buffer, " tv_out : %i", &value) == 1) 638b4f9fe12SLen Brown tv_out = value & 1; 639b4f9fe12SLen Brown /* advance to one character past the next ; */ 640b4f9fe12SLen Brown do { 641b4f9fe12SLen Brown ++buffer; 642b4f9fe12SLen Brown --remain; 643b4f9fe12SLen Brown } 644b4f9fe12SLen Brown while (remain && *(buffer - 1) != ';'); 645b4f9fe12SLen Brown } 646b4f9fe12SLen Brown 647936c8bcdSAlexey Dobriyan kfree(cmd); 648936c8bcdSAlexey Dobriyan 649b4f9fe12SLen Brown hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); 650b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) { 651b4f9fe12SLen Brown unsigned int new_video_out = video_out; 652b4f9fe12SLen Brown if (lcd_out != -1) 653b4f9fe12SLen Brown _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); 654b4f9fe12SLen Brown if (crt_out != -1) 655b4f9fe12SLen Brown _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out); 656b4f9fe12SLen Brown if (tv_out != -1) 657b4f9fe12SLen Brown _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); 658b4f9fe12SLen Brown /* To avoid unnecessary video disruption, only write the new 659b4f9fe12SLen Brown * video setting if something changed. */ 660b4f9fe12SLen Brown if (new_video_out != video_out) 661b4f9fe12SLen Brown write_acpi_int(METHOD_VIDEO_OUT, new_video_out); 662b4f9fe12SLen Brown } else { 663b4f9fe12SLen Brown return -EFAULT; 664b4f9fe12SLen Brown } 665b4f9fe12SLen Brown 666b4f9fe12SLen Brown return count; 667b4f9fe12SLen Brown } 668b4f9fe12SLen Brown 669936c8bcdSAlexey Dobriyan static const struct file_operations video_proc_fops = { 670936c8bcdSAlexey Dobriyan .owner = THIS_MODULE, 671936c8bcdSAlexey Dobriyan .open = video_proc_open, 672936c8bcdSAlexey Dobriyan .read = seq_read, 673936c8bcdSAlexey Dobriyan .llseek = seq_lseek, 674936c8bcdSAlexey Dobriyan .release = single_release, 675936c8bcdSAlexey Dobriyan .write = video_proc_write, 676936c8bcdSAlexey Dobriyan }; 677936c8bcdSAlexey Dobriyan 678936c8bcdSAlexey Dobriyan static int fan_proc_show(struct seq_file *m, void *v) 679b4f9fe12SLen Brown { 680b4f9fe12SLen Brown u32 hci_result; 681b4f9fe12SLen Brown u32 value; 682b4f9fe12SLen Brown 683b4f9fe12SLen Brown hci_read1(HCI_FAN, &value, &hci_result); 684b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) { 685936c8bcdSAlexey Dobriyan seq_printf(m, "running: %d\n", (value > 0)); 686936c8bcdSAlexey Dobriyan seq_printf(m, "force_on: %d\n", force_fan); 687b4f9fe12SLen Brown } else { 688b4f9fe12SLen Brown printk(MY_ERR "Error reading fan status\n"); 689b4f9fe12SLen Brown } 690b4f9fe12SLen Brown 691936c8bcdSAlexey Dobriyan return 0; 692b4f9fe12SLen Brown } 693b4f9fe12SLen Brown 694936c8bcdSAlexey Dobriyan static int fan_proc_open(struct inode *inode, struct file *file) 695b4f9fe12SLen Brown { 696936c8bcdSAlexey Dobriyan return single_open(file, fan_proc_show, NULL); 697936c8bcdSAlexey Dobriyan } 698936c8bcdSAlexey Dobriyan 699936c8bcdSAlexey Dobriyan static ssize_t fan_proc_write(struct file *file, const char __user *buf, 700936c8bcdSAlexey Dobriyan size_t count, loff_t *pos) 701936c8bcdSAlexey Dobriyan { 702936c8bcdSAlexey Dobriyan char cmd[42]; 703936c8bcdSAlexey Dobriyan size_t len; 704b4f9fe12SLen Brown int value; 705b4f9fe12SLen Brown u32 hci_result; 706b4f9fe12SLen Brown 707936c8bcdSAlexey Dobriyan len = min(count, sizeof(cmd) - 1); 708936c8bcdSAlexey Dobriyan if (copy_from_user(cmd, buf, len)) 709936c8bcdSAlexey Dobriyan return -EFAULT; 710936c8bcdSAlexey Dobriyan cmd[len] = '\0'; 711936c8bcdSAlexey Dobriyan 712936c8bcdSAlexey Dobriyan if (sscanf(cmd, " force_on : %i", &value) == 1 && 713b4f9fe12SLen Brown value >= 0 && value <= 1) { 714b4f9fe12SLen Brown hci_write1(HCI_FAN, value, &hci_result); 715b4f9fe12SLen Brown if (hci_result != HCI_SUCCESS) 716b4f9fe12SLen Brown return -EFAULT; 717b4f9fe12SLen Brown else 718b4f9fe12SLen Brown force_fan = value; 719b4f9fe12SLen Brown } else { 720b4f9fe12SLen Brown return -EINVAL; 721b4f9fe12SLen Brown } 722b4f9fe12SLen Brown 723b4f9fe12SLen Brown return count; 724b4f9fe12SLen Brown } 725b4f9fe12SLen Brown 726936c8bcdSAlexey Dobriyan static const struct file_operations fan_proc_fops = { 727936c8bcdSAlexey Dobriyan .owner = THIS_MODULE, 728936c8bcdSAlexey Dobriyan .open = fan_proc_open, 729936c8bcdSAlexey Dobriyan .read = seq_read, 730936c8bcdSAlexey Dobriyan .llseek = seq_lseek, 731936c8bcdSAlexey Dobriyan .release = single_release, 732936c8bcdSAlexey Dobriyan .write = fan_proc_write, 733936c8bcdSAlexey Dobriyan }; 734936c8bcdSAlexey Dobriyan 735936c8bcdSAlexey Dobriyan static int keys_proc_show(struct seq_file *m, void *v) 736b4f9fe12SLen Brown { 737b4f9fe12SLen Brown u32 hci_result; 738b4f9fe12SLen Brown u32 value; 739b4f9fe12SLen Brown 740b4f9fe12SLen Brown if (!key_event_valid) { 741b4f9fe12SLen Brown hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); 742b4f9fe12SLen Brown if (hci_result == HCI_SUCCESS) { 743b4f9fe12SLen Brown key_event_valid = 1; 744b4f9fe12SLen Brown last_key_event = value; 745b4f9fe12SLen Brown } else if (hci_result == HCI_EMPTY) { 746b4f9fe12SLen Brown /* better luck next time */ 747b4f9fe12SLen Brown } else if (hci_result == HCI_NOT_SUPPORTED) { 748b4f9fe12SLen Brown /* This is a workaround for an unresolved issue on 749b4f9fe12SLen Brown * some machines where system events sporadically 750b4f9fe12SLen Brown * become disabled. */ 751b4f9fe12SLen Brown hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); 752b4f9fe12SLen Brown printk(MY_NOTICE "Re-enabled hotkeys\n"); 753b4f9fe12SLen Brown } else { 754b4f9fe12SLen Brown printk(MY_ERR "Error reading hotkey status\n"); 755b4f9fe12SLen Brown goto end; 756b4f9fe12SLen Brown } 757b4f9fe12SLen Brown } 758b4f9fe12SLen Brown 759936c8bcdSAlexey Dobriyan seq_printf(m, "hotkey_ready: %d\n", key_event_valid); 760936c8bcdSAlexey Dobriyan seq_printf(m, "hotkey: 0x%04x\n", last_key_event); 761b4f9fe12SLen Brown end: 762936c8bcdSAlexey Dobriyan return 0; 763b4f9fe12SLen Brown } 764b4f9fe12SLen Brown 765936c8bcdSAlexey Dobriyan static int keys_proc_open(struct inode *inode, struct file *file) 766b4f9fe12SLen Brown { 767936c8bcdSAlexey Dobriyan return single_open(file, keys_proc_show, NULL); 768936c8bcdSAlexey Dobriyan } 769936c8bcdSAlexey Dobriyan 770936c8bcdSAlexey Dobriyan static ssize_t keys_proc_write(struct file *file, const char __user *buf, 771936c8bcdSAlexey Dobriyan size_t count, loff_t *pos) 772936c8bcdSAlexey Dobriyan { 773936c8bcdSAlexey Dobriyan char cmd[42]; 774936c8bcdSAlexey Dobriyan size_t len; 775b4f9fe12SLen Brown int value; 776b4f9fe12SLen Brown 777936c8bcdSAlexey Dobriyan len = min(count, sizeof(cmd) - 1); 778936c8bcdSAlexey Dobriyan if (copy_from_user(cmd, buf, len)) 779936c8bcdSAlexey Dobriyan return -EFAULT; 780936c8bcdSAlexey Dobriyan cmd[len] = '\0'; 781936c8bcdSAlexey Dobriyan 782936c8bcdSAlexey Dobriyan if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) { 783b4f9fe12SLen Brown key_event_valid = 0; 784b4f9fe12SLen Brown } else { 785b4f9fe12SLen Brown return -EINVAL; 786b4f9fe12SLen Brown } 787b4f9fe12SLen Brown 788b4f9fe12SLen Brown return count; 789b4f9fe12SLen Brown } 790b4f9fe12SLen Brown 791936c8bcdSAlexey Dobriyan static const struct file_operations keys_proc_fops = { 792936c8bcdSAlexey Dobriyan .owner = THIS_MODULE, 793936c8bcdSAlexey Dobriyan .open = keys_proc_open, 794936c8bcdSAlexey Dobriyan .read = seq_read, 795936c8bcdSAlexey Dobriyan .llseek = seq_lseek, 796936c8bcdSAlexey Dobriyan .release = single_release, 797936c8bcdSAlexey Dobriyan .write = keys_proc_write, 798936c8bcdSAlexey Dobriyan }; 799936c8bcdSAlexey Dobriyan 800936c8bcdSAlexey Dobriyan static int version_proc_show(struct seq_file *m, void *v) 801b4f9fe12SLen Brown { 802936c8bcdSAlexey Dobriyan seq_printf(m, "driver: %s\n", TOSHIBA_ACPI_VERSION); 803936c8bcdSAlexey Dobriyan seq_printf(m, "proc_interface: %d\n", PROC_INTERFACE_VERSION); 804936c8bcdSAlexey Dobriyan return 0; 805b4f9fe12SLen Brown } 806b4f9fe12SLen Brown 807936c8bcdSAlexey Dobriyan static int version_proc_open(struct inode *inode, struct file *file) 808936c8bcdSAlexey Dobriyan { 809936c8bcdSAlexey Dobriyan return single_open(file, version_proc_show, PDE(inode)->data); 810936c8bcdSAlexey Dobriyan } 811936c8bcdSAlexey Dobriyan 812936c8bcdSAlexey Dobriyan static const struct file_operations version_proc_fops = { 813936c8bcdSAlexey Dobriyan .owner = THIS_MODULE, 814936c8bcdSAlexey Dobriyan .open = version_proc_open, 815936c8bcdSAlexey Dobriyan .read = seq_read, 816936c8bcdSAlexey Dobriyan .llseek = seq_lseek, 817936c8bcdSAlexey Dobriyan .release = single_release, 818936c8bcdSAlexey Dobriyan }; 819936c8bcdSAlexey Dobriyan 820b4f9fe12SLen Brown /* proc and module init 821b4f9fe12SLen Brown */ 822b4f9fe12SLen Brown 823b4f9fe12SLen Brown #define PROC_TOSHIBA "toshiba" 824b4f9fe12SLen Brown 825f8ef3aecSAxel Lin static void __init create_toshiba_proc_entries(void) 826b4f9fe12SLen Brown { 827936c8bcdSAlexey Dobriyan proc_create("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir, &lcd_proc_fops); 828936c8bcdSAlexey Dobriyan proc_create("video", S_IRUGO | S_IWUSR, toshiba_proc_dir, &video_proc_fops); 829936c8bcdSAlexey Dobriyan proc_create("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir, &fan_proc_fops); 830936c8bcdSAlexey Dobriyan proc_create("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir, &keys_proc_fops); 831936c8bcdSAlexey Dobriyan proc_create("version", S_IRUGO, toshiba_proc_dir, &version_proc_fops); 832b4f9fe12SLen Brown } 833b4f9fe12SLen Brown 834f8ef3aecSAxel Lin static void remove_toshiba_proc_entries(void) 835b4f9fe12SLen Brown { 836936c8bcdSAlexey Dobriyan remove_proc_entry("lcd", toshiba_proc_dir); 837936c8bcdSAlexey Dobriyan remove_proc_entry("video", toshiba_proc_dir); 838936c8bcdSAlexey Dobriyan remove_proc_entry("fan", toshiba_proc_dir); 839936c8bcdSAlexey Dobriyan remove_proc_entry("keys", toshiba_proc_dir); 840936c8bcdSAlexey Dobriyan remove_proc_entry("version", toshiba_proc_dir); 841b4f9fe12SLen Brown } 842b4f9fe12SLen Brown 843b4f9fe12SLen Brown static struct backlight_ops toshiba_backlight_data = { 844b4f9fe12SLen Brown .get_brightness = get_lcd, 845b4f9fe12SLen Brown .update_status = set_lcd_status, 846b4f9fe12SLen Brown }; 847b4f9fe12SLen Brown 8486335e4d5SMatthew Garrett static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *context) 8496335e4d5SMatthew Garrett { 8506335e4d5SMatthew Garrett u32 hci_result, value; 8516335e4d5SMatthew Garrett 8526335e4d5SMatthew Garrett if (event != 0x80) 8536335e4d5SMatthew Garrett return; 8546335e4d5SMatthew Garrett do { 8556335e4d5SMatthew Garrett hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); 8566335e4d5SMatthew Garrett if (hci_result == HCI_SUCCESS) { 8576335e4d5SMatthew Garrett if (value == 0x100) 8586335e4d5SMatthew Garrett continue; 859b466301bSFrans Pop /* act on key press; ignore key release */ 860b466301bSFrans Pop if (value & 0x80) 861b466301bSFrans Pop continue; 862b466301bSFrans Pop 863*384a7cd9SDmitry Torokhov if (!sparse_keymap_report_event(toshiba_acpi.hotkey_dev, 864*384a7cd9SDmitry Torokhov value, 1, true)) { 8656335e4d5SMatthew Garrett printk(MY_INFO "Unknown key %x\n", 866b466301bSFrans Pop value); 8676335e4d5SMatthew Garrett } 8686335e4d5SMatthew Garrett } else if (hci_result == HCI_NOT_SUPPORTED) { 8696335e4d5SMatthew Garrett /* This is a workaround for an unresolved issue on 8706335e4d5SMatthew Garrett * some machines where system events sporadically 8716335e4d5SMatthew Garrett * become disabled. */ 8726335e4d5SMatthew Garrett hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); 8736335e4d5SMatthew Garrett printk(MY_NOTICE "Re-enabled hotkeys\n"); 8746335e4d5SMatthew Garrett } 8756335e4d5SMatthew Garrett } while (hci_result != HCI_EMPTY); 8766335e4d5SMatthew Garrett } 8776335e4d5SMatthew Garrett 878*384a7cd9SDmitry Torokhov static int __init toshiba_acpi_setup_keyboard(char *device) 8796335e4d5SMatthew Garrett { 8806335e4d5SMatthew Garrett acpi_status status; 881*384a7cd9SDmitry Torokhov int error; 8826335e4d5SMatthew Garrett 883*384a7cd9SDmitry Torokhov status = acpi_get_handle(NULL, device, &toshiba_acpi.handle); 8846335e4d5SMatthew Garrett if (ACPI_FAILURE(status)) { 8856335e4d5SMatthew Garrett printk(MY_INFO "Unable to get notification device\n"); 8866335e4d5SMatthew Garrett return -ENODEV; 8876335e4d5SMatthew Garrett } 8886335e4d5SMatthew Garrett 8896335e4d5SMatthew Garrett toshiba_acpi.hotkey_dev = input_allocate_device(); 8906335e4d5SMatthew Garrett if (!toshiba_acpi.hotkey_dev) { 8916335e4d5SMatthew Garrett printk(MY_INFO "Unable to register input device\n"); 8926335e4d5SMatthew Garrett return -ENOMEM; 8936335e4d5SMatthew Garrett } 8946335e4d5SMatthew Garrett 8956335e4d5SMatthew Garrett toshiba_acpi.hotkey_dev->name = "Toshiba input device"; 8966335e4d5SMatthew Garrett toshiba_acpi.hotkey_dev->phys = device; 8976335e4d5SMatthew Garrett toshiba_acpi.hotkey_dev->id.bustype = BUS_HOST; 8986335e4d5SMatthew Garrett 899*384a7cd9SDmitry Torokhov error = sparse_keymap_setup(toshiba_acpi.hotkey_dev, 900*384a7cd9SDmitry Torokhov toshiba_acpi_keymap, NULL); 901*384a7cd9SDmitry Torokhov if (error) 902*384a7cd9SDmitry Torokhov goto err_free_dev; 903*384a7cd9SDmitry Torokhov 904*384a7cd9SDmitry Torokhov status = acpi_install_notify_handler(toshiba_acpi.handle, 905*384a7cd9SDmitry Torokhov ACPI_DEVICE_NOTIFY, toshiba_acpi_notify, NULL); 906*384a7cd9SDmitry Torokhov if (ACPI_FAILURE(status)) { 907*384a7cd9SDmitry Torokhov printk(MY_INFO "Unable to install hotkey notification\n"); 908*384a7cd9SDmitry Torokhov error = -ENODEV; 909*384a7cd9SDmitry Torokhov goto err_free_keymap; 9106335e4d5SMatthew Garrett } 9116335e4d5SMatthew Garrett 912*384a7cd9SDmitry Torokhov status = acpi_evaluate_object(toshiba_acpi.handle, "ENAB", NULL, NULL); 913*384a7cd9SDmitry Torokhov if (ACPI_FAILURE(status)) { 914*384a7cd9SDmitry Torokhov printk(MY_INFO "Unable to enable hotkeys\n"); 915*384a7cd9SDmitry Torokhov error = -ENODEV; 916*384a7cd9SDmitry Torokhov goto err_remove_notify; 917*384a7cd9SDmitry Torokhov } 918*384a7cd9SDmitry Torokhov 919*384a7cd9SDmitry Torokhov error = input_register_device(toshiba_acpi.hotkey_dev); 920*384a7cd9SDmitry Torokhov if (error) { 9216335e4d5SMatthew Garrett printk(MY_INFO "Unable to register input device\n"); 922*384a7cd9SDmitry Torokhov goto err_remove_notify; 9236335e4d5SMatthew Garrett } 9246335e4d5SMatthew Garrett 9256335e4d5SMatthew Garrett return 0; 926*384a7cd9SDmitry Torokhov 927*384a7cd9SDmitry Torokhov err_remove_notify: 928*384a7cd9SDmitry Torokhov acpi_remove_notify_handler(toshiba_acpi.handle, 929*384a7cd9SDmitry Torokhov ACPI_DEVICE_NOTIFY, toshiba_acpi_notify); 930*384a7cd9SDmitry Torokhov err_free_keymap: 931*384a7cd9SDmitry Torokhov sparse_keymap_free(toshiba_acpi.hotkey_dev); 932*384a7cd9SDmitry Torokhov err_free_dev: 933*384a7cd9SDmitry Torokhov input_free_device(toshiba_acpi.hotkey_dev); 934*384a7cd9SDmitry Torokhov toshiba_acpi.hotkey_dev = NULL; 935*384a7cd9SDmitry Torokhov return error; 9366335e4d5SMatthew Garrett } 9376335e4d5SMatthew Garrett 938b4f9fe12SLen Brown static void toshiba_acpi_exit(void) 939b4f9fe12SLen Brown { 940*384a7cd9SDmitry Torokhov if (toshiba_acpi.hotkey_dev) { 941*384a7cd9SDmitry Torokhov acpi_remove_notify_handler(toshiba_acpi.handle, 942*384a7cd9SDmitry Torokhov ACPI_DEVICE_NOTIFY, toshiba_acpi_notify); 943*384a7cd9SDmitry Torokhov sparse_keymap_free(toshiba_acpi.hotkey_dev); 9446335e4d5SMatthew Garrett input_unregister_device(toshiba_acpi.hotkey_dev); 945*384a7cd9SDmitry Torokhov } 9466335e4d5SMatthew Garrett 94719d337dfSJohannes Berg if (toshiba_acpi.bt_rfk) { 94819d337dfSJohannes Berg rfkill_unregister(toshiba_acpi.bt_rfk); 94919d337dfSJohannes Berg rfkill_destroy(toshiba_acpi.bt_rfk); 950b4f9fe12SLen Brown } 951b4f9fe12SLen Brown 952b4f9fe12SLen Brown if (toshiba_backlight_device) 953b4f9fe12SLen Brown backlight_device_unregister(toshiba_backlight_device); 954b4f9fe12SLen Brown 955f8ef3aecSAxel Lin remove_toshiba_proc_entries(); 956b4f9fe12SLen Brown 957b4f9fe12SLen Brown if (toshiba_proc_dir) 958b4f9fe12SLen Brown remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); 959b4f9fe12SLen Brown 9606c3f6e6cSPierre Ducroquet if (toshiba_acpi.illumination_installed) 9616c3f6e6cSPierre Ducroquet led_classdev_unregister(&toshiba_led); 9626c3f6e6cSPierre Ducroquet 963b4f9fe12SLen Brown platform_device_unregister(toshiba_acpi.p_dev); 964b4f9fe12SLen Brown 965b4f9fe12SLen Brown return; 966b4f9fe12SLen Brown } 967b4f9fe12SLen Brown 968b4f9fe12SLen Brown static int __init toshiba_acpi_init(void) 969b4f9fe12SLen Brown { 970b4f9fe12SLen Brown u32 hci_result; 971b4f9fe12SLen Brown bool bt_present; 972b4f9fe12SLen Brown int ret = 0; 973a19a6ee6SMatthew Garrett struct backlight_properties props; 974b4f9fe12SLen Brown 975b4f9fe12SLen Brown if (acpi_disabled) 976b4f9fe12SLen Brown return -ENODEV; 977b4f9fe12SLen Brown 978b4f9fe12SLen Brown /* simple device detection: look for HCI method */ 9796335e4d5SMatthew Garrett if (is_valid_acpi_path(TOSH_INTERFACE_1 GHCI_METHOD)) { 9806335e4d5SMatthew Garrett method_hci = TOSH_INTERFACE_1 GHCI_METHOD; 9816335e4d5SMatthew Garrett if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_1)) 9826335e4d5SMatthew Garrett printk(MY_INFO "Unable to activate hotkeys\n"); 9836335e4d5SMatthew Garrett } else if (is_valid_acpi_path(TOSH_INTERFACE_2 GHCI_METHOD)) { 9846335e4d5SMatthew Garrett method_hci = TOSH_INTERFACE_2 GHCI_METHOD; 9856335e4d5SMatthew Garrett if (toshiba_acpi_setup_keyboard(TOSH_INTERFACE_2)) 9866335e4d5SMatthew Garrett printk(MY_INFO "Unable to activate hotkeys\n"); 9876335e4d5SMatthew Garrett } else 988b4f9fe12SLen Brown return -ENODEV; 989b4f9fe12SLen Brown 990b4f9fe12SLen Brown printk(MY_INFO "Toshiba Laptop ACPI Extras version %s\n", 991b4f9fe12SLen Brown TOSHIBA_ACPI_VERSION); 992b4f9fe12SLen Brown printk(MY_INFO " HCI method: %s\n", method_hci); 993b4f9fe12SLen Brown 994b4f9fe12SLen Brown mutex_init(&toshiba_acpi.mutex); 995b4f9fe12SLen Brown 996b4f9fe12SLen Brown toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi", 997b4f9fe12SLen Brown -1, NULL, 0); 998b4f9fe12SLen Brown if (IS_ERR(toshiba_acpi.p_dev)) { 999b4f9fe12SLen Brown ret = PTR_ERR(toshiba_acpi.p_dev); 1000b4f9fe12SLen Brown printk(MY_ERR "unable to register platform device\n"); 1001b4f9fe12SLen Brown toshiba_acpi.p_dev = NULL; 1002b4f9fe12SLen Brown toshiba_acpi_exit(); 1003b4f9fe12SLen Brown return ret; 1004b4f9fe12SLen Brown } 1005b4f9fe12SLen Brown 1006b4f9fe12SLen Brown force_fan = 0; 1007b4f9fe12SLen Brown key_event_valid = 0; 1008b4f9fe12SLen Brown 1009b4f9fe12SLen Brown /* enable event fifo */ 1010b4f9fe12SLen Brown hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); 1011b4f9fe12SLen Brown 1012b4f9fe12SLen Brown toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir); 1013b4f9fe12SLen Brown if (!toshiba_proc_dir) { 1014b4f9fe12SLen Brown toshiba_acpi_exit(); 1015b4f9fe12SLen Brown return -ENODEV; 1016b4f9fe12SLen Brown } else { 1017f8ef3aecSAxel Lin create_toshiba_proc_entries(); 1018b4f9fe12SLen Brown } 1019b4f9fe12SLen Brown 1020a19a6ee6SMatthew Garrett props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; 1021b4f9fe12SLen Brown toshiba_backlight_device = backlight_device_register("toshiba", 1022b4f9fe12SLen Brown &toshiba_acpi.p_dev->dev, 1023b4f9fe12SLen Brown NULL, 1024a19a6ee6SMatthew Garrett &toshiba_backlight_data, 1025a19a6ee6SMatthew Garrett &props); 1026b4f9fe12SLen Brown if (IS_ERR(toshiba_backlight_device)) { 1027b4f9fe12SLen Brown ret = PTR_ERR(toshiba_backlight_device); 1028b4f9fe12SLen Brown 1029b4f9fe12SLen Brown printk(KERN_ERR "Could not register toshiba backlight device\n"); 1030b4f9fe12SLen Brown toshiba_backlight_device = NULL; 1031b4f9fe12SLen Brown toshiba_acpi_exit(); 1032b4f9fe12SLen Brown return ret; 1033b4f9fe12SLen Brown } 1034b4f9fe12SLen Brown 1035b4f9fe12SLen Brown /* Register rfkill switch for Bluetooth */ 1036b4f9fe12SLen Brown if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) { 103719d337dfSJohannes Berg toshiba_acpi.bt_rfk = rfkill_alloc(toshiba_acpi.bt_name, 103819d337dfSJohannes Berg &toshiba_acpi.p_dev->dev, 103919d337dfSJohannes Berg RFKILL_TYPE_BLUETOOTH, 104019d337dfSJohannes Berg &toshiba_rfk_ops, 104119d337dfSJohannes Berg &toshiba_acpi); 104219d337dfSJohannes Berg if (!toshiba_acpi.bt_rfk) { 1043b4f9fe12SLen Brown printk(MY_ERR "unable to allocate rfkill device\n"); 1044b4f9fe12SLen Brown toshiba_acpi_exit(); 1045b4f9fe12SLen Brown return -ENOMEM; 1046b4f9fe12SLen Brown } 1047b4f9fe12SLen Brown 104819d337dfSJohannes Berg ret = rfkill_register(toshiba_acpi.bt_rfk); 1049b4f9fe12SLen Brown if (ret) { 1050b4f9fe12SLen Brown printk(MY_ERR "unable to register rfkill device\n"); 105119d337dfSJohannes Berg rfkill_destroy(toshiba_acpi.bt_rfk); 1052b4f9fe12SLen Brown toshiba_acpi_exit(); 1053b4f9fe12SLen Brown return ret; 1054b4f9fe12SLen Brown } 1055b4f9fe12SLen Brown } 1056b4f9fe12SLen Brown 10576c3f6e6cSPierre Ducroquet toshiba_acpi.illumination_installed = 0; 10586c3f6e6cSPierre Ducroquet if (toshiba_illumination_available()) { 10596c3f6e6cSPierre Ducroquet if (!led_classdev_register(&(toshiba_acpi.p_dev->dev), 10606c3f6e6cSPierre Ducroquet &toshiba_led)) 10616c3f6e6cSPierre Ducroquet toshiba_acpi.illumination_installed = 1; 10626c3f6e6cSPierre Ducroquet } 10636c3f6e6cSPierre Ducroquet 1064b4f9fe12SLen Brown return 0; 1065b4f9fe12SLen Brown } 1066b4f9fe12SLen Brown 1067b4f9fe12SLen Brown module_init(toshiba_acpi_init); 1068b4f9fe12SLen Brown module_exit(toshiba_acpi_exit); 1069