diff options
Diffstat (limited to 'drivers/platform')
24 files changed, 4759 insertions, 1218 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 222dfb737b11..2ee442c2a5db 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
@@ -101,6 +101,19 @@ config DELL_WMI | |||
101 | To compile this driver as a module, choose M here: the module will | 101 | To compile this driver as a module, choose M here: the module will |
102 | be called dell-wmi. | 102 | be called dell-wmi. |
103 | 103 | ||
104 | config DELL_WMI_AIO | ||
105 | tristate "WMI Hotkeys for Dell All-In-One series" | ||
106 | depends on ACPI_WMI | ||
107 | depends on INPUT | ||
108 | select INPUT_SPARSEKMAP | ||
109 | ---help--- | ||
110 | Say Y here if you want to support WMI-based hotkeys on Dell | ||
111 | All-In-One machines. | ||
112 | |||
113 | To compile this driver as a module, choose M here: the module will | ||
114 | be called dell-wmi. | ||
115 | |||
116 | |||
104 | config FUJITSU_LAPTOP | 117 | config FUJITSU_LAPTOP |
105 | tristate "Fujitsu Laptop Extras" | 118 | tristate "Fujitsu Laptop Extras" |
106 | depends on ACPI | 119 | depends on ACPI |
@@ -438,23 +451,53 @@ config EEEPC_LAPTOP | |||
438 | Bluetooth, backlight and allows powering on/off some other | 451 | Bluetooth, backlight and allows powering on/off some other |
439 | devices. | 452 | devices. |
440 | 453 | ||
441 | If you have an Eee PC laptop, say Y or M here. | 454 | If you have an Eee PC laptop, say Y or M here. If this driver |
455 | doesn't work on your Eee PC, try eeepc-wmi instead. | ||
442 | 456 | ||
443 | config EEEPC_WMI | 457 | config ASUS_WMI |
444 | tristate "Eee PC WMI Hotkey Driver (EXPERIMENTAL)" | 458 | tristate "ASUS WMI Driver (EXPERIMENTAL)" |
445 | depends on ACPI_WMI | 459 | depends on ACPI_WMI |
446 | depends on INPUT | 460 | depends on INPUT |
461 | depends on HWMON | ||
447 | depends on EXPERIMENTAL | 462 | depends on EXPERIMENTAL |
448 | depends on BACKLIGHT_CLASS_DEVICE | 463 | depends on BACKLIGHT_CLASS_DEVICE |
449 | depends on RFKILL || RFKILL = n | 464 | depends on RFKILL || RFKILL = n |
465 | depends on HOTPLUG_PCI | ||
450 | select INPUT_SPARSEKMAP | 466 | select INPUT_SPARSEKMAP |
451 | select LEDS_CLASS | 467 | select LEDS_CLASS |
452 | select NEW_LEDS | 468 | select NEW_LEDS |
453 | ---help--- | 469 | ---help--- |
454 | Say Y here if you want to support WMI-based hotkeys on Eee PC laptops. | 470 | Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new |
471 | Asus Notebooks). | ||
455 | 472 | ||
456 | To compile this driver as a module, choose M here: the module will | 473 | To compile this driver as a module, choose M here: the module will |
457 | be called eeepc-wmi. | 474 | be called asus-wmi. |
475 | |||
476 | config ASUS_NB_WMI | ||
477 | tristate "Asus Notebook WMI Driver (EXPERIMENTAL)" | ||
478 | depends on ASUS_WMI | ||
479 | ---help--- | ||
480 | This is a driver for newer Asus notebooks. It adds extra features | ||
481 | like wireless radio and bluetooth control, leds, hotkeys, backlight... | ||
482 | |||
483 | For more informations, see | ||
484 | <file:Documentation/ABI/testing/sysfs-platform-asus-wmi> | ||
485 | |||
486 | If you have an ACPI-WMI compatible Asus Notebook, say Y or M | ||
487 | here. | ||
488 | |||
489 | config EEEPC_WMI | ||
490 | tristate "Eee PC WMI Driver (EXPERIMENTAL)" | ||
491 | depends on ASUS_WMI | ||
492 | ---help--- | ||
493 | This is a driver for newer Eee PC laptops. It adds extra features | ||
494 | like wireless radio and bluetooth control, leds, hotkeys, backlight... | ||
495 | |||
496 | For more informations, see | ||
497 | <file:Documentation/ABI/testing/sysfs-platform-asus-wmi> | ||
498 | |||
499 | If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M | ||
500 | here. | ||
458 | 501 | ||
459 | config ACPI_WMI | 502 | config ACPI_WMI |
460 | tristate "WMI" | 503 | tristate "WMI" |
@@ -616,6 +659,21 @@ config GPIO_INTEL_PMIC | |||
616 | Say Y here to support GPIO via the SCU IPC interface | 659 | Say Y here to support GPIO via the SCU IPC interface |
617 | on Intel MID platforms. | 660 | on Intel MID platforms. |
618 | 661 | ||
662 | config INTEL_MID_POWER_BUTTON | ||
663 | tristate "power button driver for Intel MID platforms" | ||
664 | depends on INTEL_SCU_IPC && INPUT | ||
665 | help | ||
666 | This driver handles the power button on the Intel MID platforms. | ||
667 | |||
668 | If unsure, say N. | ||
669 | |||
670 | config INTEL_MFLD_THERMAL | ||
671 | tristate "Thermal driver for Intel Medfield platform" | ||
672 | depends on INTEL_SCU_IPC && THERMAL | ||
673 | help | ||
674 | Say Y here to enable thermal driver support for the Intel Medfield | ||
675 | platform. | ||
676 | |||
619 | config RAR_REGISTER | 677 | config RAR_REGISTER |
620 | bool "Restricted Access Region Register Driver" | 678 | bool "Restricted Access Region Register Driver" |
621 | depends on PCI && X86_MRST | 679 | depends on PCI && X86_MRST |
@@ -672,4 +730,26 @@ config XO1_RFKILL | |||
672 | Support for enabling/disabling the WLAN interface on the OLPC XO-1 | 730 | Support for enabling/disabling the WLAN interface on the OLPC XO-1 |
673 | laptop. | 731 | laptop. |
674 | 732 | ||
733 | config XO15_EBOOK | ||
734 | tristate "OLPC XO-1.5 ebook switch" | ||
735 | depends on ACPI && INPUT | ||
736 | ---help--- | ||
737 | Support for the ebook switch on the OLPC XO-1.5 laptop. | ||
738 | |||
739 | This switch is triggered as the screen is rotated and folded down to | ||
740 | convert the device into ebook form. | ||
741 | |||
742 | config SAMSUNG_LAPTOP | ||
743 | tristate "Samsung Laptop driver" | ||
744 | depends on RFKILL && BACKLIGHT_CLASS_DEVICE && X86 | ||
745 | ---help--- | ||
746 | This module implements a driver for a wide range of different | ||
747 | Samsung laptops. It offers control over the different | ||
748 | function keys, wireless LED, LCD backlight level, and | ||
749 | sometimes provides a "performance_control" sysfs file to allow | ||
750 | the performance level of the laptop to be changed. | ||
751 | |||
752 | To compile this driver as a module, choose M here: the module | ||
753 | will be called samsung-laptop. | ||
754 | |||
675 | endif # X86_PLATFORM_DEVICES | 755 | endif # X86_PLATFORM_DEVICES |
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 299aefb3e74c..029e8861d086 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile | |||
@@ -3,6 +3,8 @@ | |||
3 | # x86 Platform-Specific Drivers | 3 | # x86 Platform-Specific Drivers |
4 | # | 4 | # |
5 | obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o | 5 | obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o |
6 | obj-$(CONFIG_ASUS_WMI) += asus-wmi.o | ||
7 | obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o | ||
6 | obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o | 8 | obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o |
7 | obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o | 9 | obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o |
8 | obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o | 10 | obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o |
@@ -10,6 +12,7 @@ obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o | |||
10 | obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o | 12 | obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o |
11 | obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o | 13 | obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o |
12 | obj-$(CONFIG_DELL_WMI) += dell-wmi.o | 14 | obj-$(CONFIG_DELL_WMI) += dell-wmi.o |
15 | obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o | ||
13 | obj-$(CONFIG_ACER_WMI) += acer-wmi.o | 16 | obj-$(CONFIG_ACER_WMI) += acer-wmi.o |
14 | obj-$(CONFIG_ACERHDF) += acerhdf.o | 17 | obj-$(CONFIG_ACERHDF) += acerhdf.o |
15 | obj-$(CONFIG_HP_ACCEL) += hp_accel.o | 18 | obj-$(CONFIG_HP_ACCEL) += hp_accel.o |
@@ -29,9 +32,13 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o | |||
29 | obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o | 32 | obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o |
30 | obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o | 33 | obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o |
31 | obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o | 34 | obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o |
32 | obj-$(CONFIG_INTEL_SCU_IPC_UTIL)+= intel_scu_ipcutil.o | 35 | obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o |
36 | obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o | ||
33 | obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.o | 37 | obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.o |
34 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o | 38 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o |
35 | obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o | 39 | obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o |
36 | obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o | 40 | obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o |
41 | obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o | ||
37 | obj-$(CONFIG_IBM_RTL) += ibm_rtl.o | 42 | obj-$(CONFIG_IBM_RTL) += ibm_rtl.o |
43 | obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o | ||
44 | obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o | ||
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index c9784705f6ac..5ea6c3477d17 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c | |||
@@ -22,6 +22,8 @@ | |||
22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
23 | */ | 23 | */ |
24 | 24 | ||
25 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
26 | |||
25 | #include <linux/kernel.h> | 27 | #include <linux/kernel.h> |
26 | #include <linux/module.h> | 28 | #include <linux/module.h> |
27 | #include <linux/init.h> | 29 | #include <linux/init.h> |
@@ -46,12 +48,6 @@ MODULE_AUTHOR("Carlos Corbacho"); | |||
46 | MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); | 48 | MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); |
47 | MODULE_LICENSE("GPL"); | 49 | MODULE_LICENSE("GPL"); |
48 | 50 | ||
49 | #define ACER_LOGPREFIX "acer-wmi: " | ||
50 | #define ACER_ERR KERN_ERR ACER_LOGPREFIX | ||
51 | #define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX | ||
52 | #define ACER_INFO KERN_INFO ACER_LOGPREFIX | ||
53 | #define ACER_WARNING KERN_WARNING ACER_LOGPREFIX | ||
54 | |||
55 | /* | 51 | /* |
56 | * Magic Number | 52 | * Magic Number |
57 | * Meaning is unknown - this number is required for writing to ACPI for AMW0 | 53 | * Meaning is unknown - this number is required for writing to ACPI for AMW0 |
@@ -84,7 +80,7 @@ MODULE_LICENSE("GPL"); | |||
84 | #define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB" | 80 | #define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB" |
85 | #define AMW0_GUID2 "431F16ED-0C2B-444C-B267-27DEB140CF9C" | 81 | #define AMW0_GUID2 "431F16ED-0C2B-444C-B267-27DEB140CF9C" |
86 | #define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3" | 82 | #define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3" |
87 | #define WMID_GUID2 "95764E09-FB56-4e83-B31A-37761F60994A" | 83 | #define WMID_GUID2 "95764E09-FB56-4E83-B31A-37761F60994A" |
88 | #define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" | 84 | #define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" |
89 | 85 | ||
90 | /* | 86 | /* |
@@ -93,7 +89,7 @@ MODULE_LICENSE("GPL"); | |||
93 | #define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026" | 89 | #define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026" |
94 | 90 | ||
95 | MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB"); | 91 | MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB"); |
96 | MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"); | 92 | MODULE_ALIAS("wmi:6AF4F258-B401-42Fd-BE91-3D4AC2D7C0D3"); |
97 | MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); | 93 | MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); |
98 | 94 | ||
99 | enum acer_wmi_event_ids { | 95 | enum acer_wmi_event_ids { |
@@ -108,7 +104,7 @@ static const struct key_entry acer_wmi_keymap[] = { | |||
108 | {KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */ | 104 | {KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */ |
109 | {KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */ | 105 | {KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */ |
110 | {KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */ | 106 | {KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */ |
111 | {KE_KEY, 0x82, {KEY_F22} }, /* Touch Pad On/Off */ | 107 | {KE_KEY, 0x82, {KEY_TOUCHPAD_TOGGLE} }, /* Touch Pad On/Off */ |
112 | {KE_END, 0} | 108 | {KE_END, 0} |
113 | }; | 109 | }; |
114 | 110 | ||
@@ -221,6 +217,7 @@ struct acer_debug { | |||
221 | static struct rfkill *wireless_rfkill; | 217 | static struct rfkill *wireless_rfkill; |
222 | static struct rfkill *bluetooth_rfkill; | 218 | static struct rfkill *bluetooth_rfkill; |
223 | static struct rfkill *threeg_rfkill; | 219 | static struct rfkill *threeg_rfkill; |
220 | static bool rfkill_inited; | ||
224 | 221 | ||
225 | /* Each low-level interface must define at least some of the following */ | 222 | /* Each low-level interface must define at least some of the following */ |
226 | struct wmi_interface { | 223 | struct wmi_interface { |
@@ -845,7 +842,7 @@ static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy) | |||
845 | has_type_aa = true; | 842 | has_type_aa = true; |
846 | type_aa = (struct hotkey_function_type_aa *) header; | 843 | type_aa = (struct hotkey_function_type_aa *) header; |
847 | 844 | ||
848 | printk(ACER_INFO "Function bitmap for Communication Button: 0x%x\n", | 845 | pr_info("Function bitmap for Communication Button: 0x%x\n", |
849 | type_aa->commun_func_bitmap); | 846 | type_aa->commun_func_bitmap); |
850 | 847 | ||
851 | if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS) | 848 | if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS) |
@@ -991,6 +988,7 @@ static int __devinit acer_led_init(struct device *dev) | |||
991 | 988 | ||
992 | static void acer_led_exit(void) | 989 | static void acer_led_exit(void) |
993 | { | 990 | { |
991 | set_u32(LED_OFF, ACER_CAP_MAILLED); | ||
994 | led_classdev_unregister(&mail_led); | 992 | led_classdev_unregister(&mail_led); |
995 | } | 993 | } |
996 | 994 | ||
@@ -1036,7 +1034,7 @@ static int __devinit acer_backlight_init(struct device *dev) | |||
1036 | bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops, | 1034 | bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops, |
1037 | &props); | 1035 | &props); |
1038 | if (IS_ERR(bd)) { | 1036 | if (IS_ERR(bd)) { |
1039 | printk(ACER_ERR "Could not register Acer backlight device\n"); | 1037 | pr_err("Could not register Acer backlight device\n"); |
1040 | acer_backlight_device = NULL; | 1038 | acer_backlight_device = NULL; |
1041 | return PTR_ERR(bd); | 1039 | return PTR_ERR(bd); |
1042 | } | 1040 | } |
@@ -1083,8 +1081,7 @@ static acpi_status wmid3_get_device_status(u32 *value, u16 device) | |||
1083 | return AE_ERROR; | 1081 | return AE_ERROR; |
1084 | } | 1082 | } |
1085 | if (obj->buffer.length != 8) { | 1083 | if (obj->buffer.length != 8) { |
1086 | printk(ACER_WARNING "Unknown buffer length %d\n", | 1084 | pr_warning("Unknown buffer length %d\n", obj->buffer.length); |
1087 | obj->buffer.length); | ||
1088 | kfree(obj); | 1085 | kfree(obj); |
1089 | return AE_ERROR; | 1086 | return AE_ERROR; |
1090 | } | 1087 | } |
@@ -1093,7 +1090,7 @@ static acpi_status wmid3_get_device_status(u32 *value, u16 device) | |||
1093 | kfree(obj); | 1090 | kfree(obj); |
1094 | 1091 | ||
1095 | if (return_value.error_code || return_value.ec_return_value) | 1092 | if (return_value.error_code || return_value.ec_return_value) |
1096 | printk(ACER_WARNING "Get Device Status failed: " | 1093 | pr_warning("Get Device Status failed: " |
1097 | "0x%x - 0x%x\n", return_value.error_code, | 1094 | "0x%x - 0x%x\n", return_value.error_code, |
1098 | return_value.ec_return_value); | 1095 | return_value.ec_return_value); |
1099 | else | 1096 | else |
@@ -1161,9 +1158,13 @@ static int acer_rfkill_set(void *data, bool blocked) | |||
1161 | { | 1158 | { |
1162 | acpi_status status; | 1159 | acpi_status status; |
1163 | u32 cap = (unsigned long)data; | 1160 | u32 cap = (unsigned long)data; |
1164 | status = set_u32(!blocked, cap); | 1161 | |
1165 | if (ACPI_FAILURE(status)) | 1162 | if (rfkill_inited) { |
1166 | return -ENODEV; | 1163 | status = set_u32(!blocked, cap); |
1164 | if (ACPI_FAILURE(status)) | ||
1165 | return -ENODEV; | ||
1166 | } | ||
1167 | |||
1167 | return 0; | 1168 | return 0; |
1168 | } | 1169 | } |
1169 | 1170 | ||
@@ -1187,14 +1188,16 @@ static struct rfkill *acer_rfkill_register(struct device *dev, | |||
1187 | return ERR_PTR(-ENOMEM); | 1188 | return ERR_PTR(-ENOMEM); |
1188 | 1189 | ||
1189 | status = get_device_status(&state, cap); | 1190 | status = get_device_status(&state, cap); |
1190 | if (ACPI_SUCCESS(status)) | ||
1191 | rfkill_init_sw_state(rfkill_dev, !state); | ||
1192 | 1191 | ||
1193 | err = rfkill_register(rfkill_dev); | 1192 | err = rfkill_register(rfkill_dev); |
1194 | if (err) { | 1193 | if (err) { |
1195 | rfkill_destroy(rfkill_dev); | 1194 | rfkill_destroy(rfkill_dev); |
1196 | return ERR_PTR(err); | 1195 | return ERR_PTR(err); |
1197 | } | 1196 | } |
1197 | |||
1198 | if (ACPI_SUCCESS(status)) | ||
1199 | rfkill_set_sw_state(rfkill_dev, !state); | ||
1200 | |||
1198 | return rfkill_dev; | 1201 | return rfkill_dev; |
1199 | } | 1202 | } |
1200 | 1203 | ||
@@ -1229,14 +1232,19 @@ static int acer_rfkill_init(struct device *dev) | |||
1229 | } | 1232 | } |
1230 | } | 1233 | } |
1231 | 1234 | ||
1232 | schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); | 1235 | rfkill_inited = true; |
1236 | |||
1237 | if (ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) | ||
1238 | schedule_delayed_work(&acer_rfkill_work, | ||
1239 | round_jiffies_relative(HZ)); | ||
1233 | 1240 | ||
1234 | return 0; | 1241 | return 0; |
1235 | } | 1242 | } |
1236 | 1243 | ||
1237 | static void acer_rfkill_exit(void) | 1244 | static void acer_rfkill_exit(void) |
1238 | { | 1245 | { |
1239 | cancel_delayed_work_sync(&acer_rfkill_work); | 1246 | if (ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) |
1247 | cancel_delayed_work_sync(&acer_rfkill_work); | ||
1240 | 1248 | ||
1241 | rfkill_unregister(wireless_rfkill); | 1249 | rfkill_unregister(wireless_rfkill); |
1242 | rfkill_destroy(wireless_rfkill); | 1250 | rfkill_destroy(wireless_rfkill); |
@@ -1309,7 +1317,7 @@ static void acer_wmi_notify(u32 value, void *context) | |||
1309 | 1317 | ||
1310 | status = wmi_get_event_data(value, &response); | 1318 | status = wmi_get_event_data(value, &response); |
1311 | if (status != AE_OK) { | 1319 | if (status != AE_OK) { |
1312 | printk(ACER_WARNING "bad event status 0x%x\n", status); | 1320 | pr_warning("bad event status 0x%x\n", status); |
1313 | return; | 1321 | return; |
1314 | } | 1322 | } |
1315 | 1323 | ||
@@ -1318,14 +1326,12 @@ static void acer_wmi_notify(u32 value, void *context) | |||
1318 | if (!obj) | 1326 | if (!obj) |
1319 | return; | 1327 | return; |
1320 | if (obj->type != ACPI_TYPE_BUFFER) { | 1328 | if (obj->type != ACPI_TYPE_BUFFER) { |
1321 | printk(ACER_WARNING "Unknown response received %d\n", | 1329 | pr_warning("Unknown response received %d\n", obj->type); |
1322 | obj->type); | ||
1323 | kfree(obj); | 1330 | kfree(obj); |
1324 | return; | 1331 | return; |
1325 | } | 1332 | } |
1326 | if (obj->buffer.length != 8) { | 1333 | if (obj->buffer.length != 8) { |
1327 | printk(ACER_WARNING "Unknown buffer length %d\n", | 1334 | pr_warning("Unknown buffer length %d\n", obj->buffer.length); |
1328 | obj->buffer.length); | ||
1329 | kfree(obj); | 1335 | kfree(obj); |
1330 | return; | 1336 | return; |
1331 | } | 1337 | } |
@@ -1335,13 +1341,26 @@ static void acer_wmi_notify(u32 value, void *context) | |||
1335 | 1341 | ||
1336 | switch (return_value.function) { | 1342 | switch (return_value.function) { |
1337 | case WMID_HOTKEY_EVENT: | 1343 | case WMID_HOTKEY_EVENT: |
1344 | if (return_value.device_state) { | ||
1345 | u16 device_state = return_value.device_state; | ||
1346 | pr_debug("deivces states: 0x%x\n", device_state); | ||
1347 | if (has_cap(ACER_CAP_WIRELESS)) | ||
1348 | rfkill_set_sw_state(wireless_rfkill, | ||
1349 | !(device_state & ACER_WMID3_GDS_WIRELESS)); | ||
1350 | if (has_cap(ACER_CAP_BLUETOOTH)) | ||
1351 | rfkill_set_sw_state(bluetooth_rfkill, | ||
1352 | !(device_state & ACER_WMID3_GDS_BLUETOOTH)); | ||
1353 | if (has_cap(ACER_CAP_THREEG)) | ||
1354 | rfkill_set_sw_state(threeg_rfkill, | ||
1355 | !(device_state & ACER_WMID3_GDS_THREEG)); | ||
1356 | } | ||
1338 | if (!sparse_keymap_report_event(acer_wmi_input_dev, | 1357 | if (!sparse_keymap_report_event(acer_wmi_input_dev, |
1339 | return_value.key_num, 1, true)) | 1358 | return_value.key_num, 1, true)) |
1340 | printk(ACER_WARNING "Unknown key number - 0x%x\n", | 1359 | pr_warning("Unknown key number - 0x%x\n", |
1341 | return_value.key_num); | 1360 | return_value.key_num); |
1342 | break; | 1361 | break; |
1343 | default: | 1362 | default: |
1344 | printk(ACER_WARNING "Unknown function number - %d - %d\n", | 1363 | pr_warning("Unknown function number - %d - %d\n", |
1345 | return_value.function, return_value.key_num); | 1364 | return_value.function, return_value.key_num); |
1346 | break; | 1365 | break; |
1347 | } | 1366 | } |
@@ -1370,8 +1389,7 @@ wmid3_set_lm_mode(struct lm_input_params *params, | |||
1370 | return AE_ERROR; | 1389 | return AE_ERROR; |
1371 | } | 1390 | } |
1372 | if (obj->buffer.length != 4) { | 1391 | if (obj->buffer.length != 4) { |
1373 | printk(ACER_WARNING "Unknown buffer length %d\n", | 1392 | pr_warning("Unknown buffer length %d\n", obj->buffer.length); |
1374 | obj->buffer.length); | ||
1375 | kfree(obj); | 1393 | kfree(obj); |
1376 | return AE_ERROR; | 1394 | return AE_ERROR; |
1377 | } | 1395 | } |
@@ -1396,11 +1414,11 @@ static int acer_wmi_enable_ec_raw(void) | |||
1396 | status = wmid3_set_lm_mode(¶ms, &return_value); | 1414 | status = wmid3_set_lm_mode(¶ms, &return_value); |
1397 | 1415 | ||
1398 | if (return_value.error_code || return_value.ec_return_value) | 1416 | if (return_value.error_code || return_value.ec_return_value) |
1399 | printk(ACER_WARNING "Enabling EC raw mode failed: " | 1417 | pr_warning("Enabling EC raw mode failed: " |
1400 | "0x%x - 0x%x\n", return_value.error_code, | 1418 | "0x%x - 0x%x\n", return_value.error_code, |
1401 | return_value.ec_return_value); | 1419 | return_value.ec_return_value); |
1402 | else | 1420 | else |
1403 | printk(ACER_INFO "Enabled EC raw mode"); | 1421 | pr_info("Enabled EC raw mode"); |
1404 | 1422 | ||
1405 | return status; | 1423 | return status; |
1406 | } | 1424 | } |
@@ -1419,7 +1437,7 @@ static int acer_wmi_enable_lm(void) | |||
1419 | status = wmid3_set_lm_mode(¶ms, &return_value); | 1437 | status = wmid3_set_lm_mode(¶ms, &return_value); |
1420 | 1438 | ||
1421 | if (return_value.error_code || return_value.ec_return_value) | 1439 | if (return_value.error_code || return_value.ec_return_value) |
1422 | printk(ACER_WARNING "Enabling Launch Manager failed: " | 1440 | pr_warning("Enabling Launch Manager failed: " |
1423 | "0x%x - 0x%x\n", return_value.error_code, | 1441 | "0x%x - 0x%x\n", return_value.error_code, |
1424 | return_value.ec_return_value); | 1442 | return_value.ec_return_value); |
1425 | 1443 | ||
@@ -1553,6 +1571,7 @@ pm_message_t state) | |||
1553 | 1571 | ||
1554 | if (has_cap(ACER_CAP_MAILLED)) { | 1572 | if (has_cap(ACER_CAP_MAILLED)) { |
1555 | get_u32(&value, ACER_CAP_MAILLED); | 1573 | get_u32(&value, ACER_CAP_MAILLED); |
1574 | set_u32(LED_OFF, ACER_CAP_MAILLED); | ||
1556 | data->mailled = value; | 1575 | data->mailled = value; |
1557 | } | 1576 | } |
1558 | 1577 | ||
@@ -1580,6 +1599,17 @@ static int acer_platform_resume(struct platform_device *device) | |||
1580 | return 0; | 1599 | return 0; |
1581 | } | 1600 | } |
1582 | 1601 | ||
1602 | static void acer_platform_shutdown(struct platform_device *device) | ||
1603 | { | ||
1604 | struct acer_data *data = &interface->data; | ||
1605 | |||
1606 | if (!data) | ||
1607 | return; | ||
1608 | |||
1609 | if (has_cap(ACER_CAP_MAILLED)) | ||
1610 | set_u32(LED_OFF, ACER_CAP_MAILLED); | ||
1611 | } | ||
1612 | |||
1583 | static struct platform_driver acer_platform_driver = { | 1613 | static struct platform_driver acer_platform_driver = { |
1584 | .driver = { | 1614 | .driver = { |
1585 | .name = "acer-wmi", | 1615 | .name = "acer-wmi", |
@@ -1589,6 +1619,7 @@ static struct platform_driver acer_platform_driver = { | |||
1589 | .remove = acer_platform_remove, | 1619 | .remove = acer_platform_remove, |
1590 | .suspend = acer_platform_suspend, | 1620 | .suspend = acer_platform_suspend, |
1591 | .resume = acer_platform_resume, | 1621 | .resume = acer_platform_resume, |
1622 | .shutdown = acer_platform_shutdown, | ||
1592 | }; | 1623 | }; |
1593 | 1624 | ||
1594 | static struct platform_device *acer_platform_device; | 1625 | static struct platform_device *acer_platform_device; |
@@ -1636,7 +1667,7 @@ static int create_debugfs(void) | |||
1636 | { | 1667 | { |
1637 | interface->debug.root = debugfs_create_dir("acer-wmi", NULL); | 1668 | interface->debug.root = debugfs_create_dir("acer-wmi", NULL); |
1638 | if (!interface->debug.root) { | 1669 | if (!interface->debug.root) { |
1639 | printk(ACER_ERR "Failed to create debugfs directory"); | 1670 | pr_err("Failed to create debugfs directory"); |
1640 | return -ENOMEM; | 1671 | return -ENOMEM; |
1641 | } | 1672 | } |
1642 | 1673 | ||
@@ -1657,11 +1688,10 @@ static int __init acer_wmi_init(void) | |||
1657 | { | 1688 | { |
1658 | int err; | 1689 | int err; |
1659 | 1690 | ||
1660 | printk(ACER_INFO "Acer Laptop ACPI-WMI Extras\n"); | 1691 | pr_info("Acer Laptop ACPI-WMI Extras\n"); |
1661 | 1692 | ||
1662 | if (dmi_check_system(acer_blacklist)) { | 1693 | if (dmi_check_system(acer_blacklist)) { |
1663 | printk(ACER_INFO "Blacklisted hardware detected - " | 1694 | pr_info("Blacklisted hardware detected - not loading\n"); |
1664 | "not loading\n"); | ||
1665 | return -ENODEV; | 1695 | return -ENODEV; |
1666 | } | 1696 | } |
1667 | 1697 | ||
@@ -1678,12 +1708,11 @@ static int __init acer_wmi_init(void) | |||
1678 | 1708 | ||
1679 | if (wmi_has_guid(WMID_GUID2) && interface) { | 1709 | if (wmi_has_guid(WMID_GUID2) && interface) { |
1680 | if (ACPI_FAILURE(WMID_set_capabilities())) { | 1710 | if (ACPI_FAILURE(WMID_set_capabilities())) { |
1681 | printk(ACER_ERR "Unable to detect available WMID " | 1711 | pr_err("Unable to detect available WMID devices\n"); |
1682 | "devices\n"); | ||
1683 | return -ENODEV; | 1712 | return -ENODEV; |
1684 | } | 1713 | } |
1685 | } else if (!wmi_has_guid(WMID_GUID2) && interface) { | 1714 | } else if (!wmi_has_guid(WMID_GUID2) && interface) { |
1686 | printk(ACER_ERR "No WMID device detection method found\n"); | 1715 | pr_err("No WMID device detection method found\n"); |
1687 | return -ENODEV; | 1716 | return -ENODEV; |
1688 | } | 1717 | } |
1689 | 1718 | ||
@@ -1691,8 +1720,7 @@ static int __init acer_wmi_init(void) | |||
1691 | interface = &AMW0_interface; | 1720 | interface = &AMW0_interface; |
1692 | 1721 | ||
1693 | if (ACPI_FAILURE(AMW0_set_capabilities())) { | 1722 | if (ACPI_FAILURE(AMW0_set_capabilities())) { |
1694 | printk(ACER_ERR "Unable to detect available AMW0 " | 1723 | pr_err("Unable to detect available AMW0 devices\n"); |
1695 | "devices\n"); | ||
1696 | return -ENODEV; | 1724 | return -ENODEV; |
1697 | } | 1725 | } |
1698 | } | 1726 | } |
@@ -1701,8 +1729,7 @@ static int __init acer_wmi_init(void) | |||
1701 | AMW0_find_mailled(); | 1729 | AMW0_find_mailled(); |
1702 | 1730 | ||
1703 | if (!interface) { | 1731 | if (!interface) { |
1704 | printk(ACER_INFO "No or unsupported WMI interface, unable to " | 1732 | pr_err("No or unsupported WMI interface, unable to load\n"); |
1705 | "load\n"); | ||
1706 | return -ENODEV; | 1733 | return -ENODEV; |
1707 | } | 1734 | } |
1708 | 1735 | ||
@@ -1710,22 +1737,22 @@ static int __init acer_wmi_init(void) | |||
1710 | 1737 | ||
1711 | if (acpi_video_backlight_support() && has_cap(ACER_CAP_BRIGHTNESS)) { | 1738 | if (acpi_video_backlight_support() && has_cap(ACER_CAP_BRIGHTNESS)) { |
1712 | interface->capability &= ~ACER_CAP_BRIGHTNESS; | 1739 | interface->capability &= ~ACER_CAP_BRIGHTNESS; |
1713 | printk(ACER_INFO "Brightness must be controlled by " | 1740 | pr_info("Brightness must be controlled by " |
1714 | "generic video driver\n"); | 1741 | "generic video driver\n"); |
1715 | } | 1742 | } |
1716 | 1743 | ||
1717 | if (wmi_has_guid(WMID_GUID3)) { | 1744 | if (wmi_has_guid(WMID_GUID3)) { |
1718 | if (ec_raw_mode) { | 1745 | if (ec_raw_mode) { |
1719 | if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) { | 1746 | if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) { |
1720 | printk(ACER_ERR "Cannot enable EC raw mode\n"); | 1747 | pr_err("Cannot enable EC raw mode\n"); |
1721 | return -ENODEV; | 1748 | return -ENODEV; |
1722 | } | 1749 | } |
1723 | } else if (ACPI_FAILURE(acer_wmi_enable_lm())) { | 1750 | } else if (ACPI_FAILURE(acer_wmi_enable_lm())) { |
1724 | printk(ACER_ERR "Cannot enable Launch Manager mode\n"); | 1751 | pr_err("Cannot enable Launch Manager mode\n"); |
1725 | return -ENODEV; | 1752 | return -ENODEV; |
1726 | } | 1753 | } |
1727 | } else if (ec_raw_mode) { | 1754 | } else if (ec_raw_mode) { |
1728 | printk(ACER_INFO "No WMID EC raw mode enable method\n"); | 1755 | pr_info("No WMID EC raw mode enable method\n"); |
1729 | } | 1756 | } |
1730 | 1757 | ||
1731 | if (wmi_has_guid(ACERWMID_EVENT_GUID)) { | 1758 | if (wmi_has_guid(ACERWMID_EVENT_GUID)) { |
@@ -1736,7 +1763,7 @@ static int __init acer_wmi_init(void) | |||
1736 | 1763 | ||
1737 | err = platform_driver_register(&acer_platform_driver); | 1764 | err = platform_driver_register(&acer_platform_driver); |
1738 | if (err) { | 1765 | if (err) { |
1739 | printk(ACER_ERR "Unable to register platform driver.\n"); | 1766 | pr_err("Unable to register platform driver.\n"); |
1740 | goto error_platform_register; | 1767 | goto error_platform_register; |
1741 | } | 1768 | } |
1742 | 1769 | ||
@@ -1791,7 +1818,7 @@ static void __exit acer_wmi_exit(void) | |||
1791 | platform_device_unregister(acer_platform_device); | 1818 | platform_device_unregister(acer_platform_device); |
1792 | platform_driver_unregister(&acer_platform_driver); | 1819 | platform_driver_unregister(&acer_platform_driver); |
1793 | 1820 | ||
1794 | printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n"); | 1821 | pr_info("Acer Laptop WMI Extras unloaded\n"); |
1795 | return; | 1822 | return; |
1796 | } | 1823 | } |
1797 | 1824 | ||
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 5a6f7d7575d6..c53b3ff7978a 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c | |||
@@ -29,7 +29,7 @@ | |||
29 | * John Belmonte - ACPI code for Toshiba laptop was a good starting point. | 29 | * John Belmonte - ACPI code for Toshiba laptop was a good starting point. |
30 | * Eric Burghard - LED display support for W1N | 30 | * Eric Burghard - LED display support for W1N |
31 | * Josh Green - Light Sens support | 31 | * Josh Green - Light Sens support |
32 | * Thomas Tuttle - His first patch for led support was very helpfull | 32 | * Thomas Tuttle - His first patch for led support was very helpful |
33 | * Sam Lin - GPS support | 33 | * Sam Lin - GPS support |
34 | */ | 34 | */ |
35 | 35 | ||
@@ -50,6 +50,7 @@ | |||
50 | #include <linux/input/sparse-keymap.h> | 50 | #include <linux/input/sparse-keymap.h> |
51 | #include <linux/rfkill.h> | 51 | #include <linux/rfkill.h> |
52 | #include <linux/slab.h> | 52 | #include <linux/slab.h> |
53 | #include <linux/dmi.h> | ||
53 | #include <acpi/acpi_drivers.h> | 54 | #include <acpi/acpi_drivers.h> |
54 | #include <acpi/acpi_bus.h> | 55 | #include <acpi/acpi_bus.h> |
55 | 56 | ||
@@ -157,46 +158,9 @@ MODULE_PARM_DESC(wwan_status, "Set the wireless status on boot " | |||
157 | #define METHOD_BRIGHTNESS_SET "SPLV" | 158 | #define METHOD_BRIGHTNESS_SET "SPLV" |
158 | #define METHOD_BRIGHTNESS_GET "GPLV" | 159 | #define METHOD_BRIGHTNESS_GET "GPLV" |
159 | 160 | ||
160 | /* Backlight */ | ||
161 | static acpi_handle lcd_switch_handle; | ||
162 | static char *lcd_switch_paths[] = { | ||
163 | "\\_SB.PCI0.SBRG.EC0._Q10", /* All new models */ | ||
164 | "\\_SB.PCI0.ISA.EC0._Q10", /* A1x */ | ||
165 | "\\_SB.PCI0.PX40.ECD0._Q10", /* L3C */ | ||
166 | "\\_SB.PCI0.PX40.EC0.Q10", /* M1A */ | ||
167 | "\\_SB.PCI0.LPCB.EC0._Q10", /* P30 */ | ||
168 | "\\_SB.PCI0.LPCB.EC0._Q0E", /* P30/P35 */ | ||
169 | "\\_SB.PCI0.PX40.Q10", /* S1x */ | ||
170 | "\\Q10"}; /* A2x, L2D, L3D, M2E */ | ||
171 | |||
172 | /* Display */ | 161 | /* Display */ |
173 | #define METHOD_SWITCH_DISPLAY "SDSP" | 162 | #define METHOD_SWITCH_DISPLAY "SDSP" |
174 | 163 | ||
175 | static acpi_handle display_get_handle; | ||
176 | static char *display_get_paths[] = { | ||
177 | /* A6B, A6K A6R A7D F3JM L4R M6R A3G M6A M6V VX-1 V6J V6V W3Z */ | ||
178 | "\\_SB.PCI0.P0P1.VGA.GETD", | ||
179 | /* A3E A4K, A4D A4L A6J A7J A8J Z71V M9V S5A M5A z33A W1Jc W2V G1 */ | ||
180 | "\\_SB.PCI0.P0P2.VGA.GETD", | ||
181 | /* A6V A6Q */ | ||
182 | "\\_SB.PCI0.P0P3.VGA.GETD", | ||
183 | /* A6T, A6M */ | ||
184 | "\\_SB.PCI0.P0PA.VGA.GETD", | ||
185 | /* L3C */ | ||
186 | "\\_SB.PCI0.PCI1.VGAC.NMAP", | ||
187 | /* Z96F */ | ||
188 | "\\_SB.PCI0.VGA.GETD", | ||
189 | /* A2D */ | ||
190 | "\\ACTD", | ||
191 | /* A4G Z71A W1N W5A W5F M2N M3N M5N M6N S1N S5N */ | ||
192 | "\\ADVG", | ||
193 | /* P30 */ | ||
194 | "\\DNXT", | ||
195 | /* A2H D1 L2D L3D L3H L2E L5D L5C M1A M2E L4L W3V */ | ||
196 | "\\INFB", | ||
197 | /* A3F A6F A3N A3L M6N W3N W6A */ | ||
198 | "\\SSTE"}; | ||
199 | |||
200 | #define METHOD_ALS_CONTROL "ALSC" /* Z71A Z71V */ | 164 | #define METHOD_ALS_CONTROL "ALSC" /* Z71A Z71V */ |
201 | #define METHOD_ALS_LEVEL "ALSL" /* Z71A Z71V */ | 165 | #define METHOD_ALS_LEVEL "ALSL" /* Z71A Z71V */ |
202 | 166 | ||
@@ -246,7 +210,6 @@ struct asus_laptop { | |||
246 | 210 | ||
247 | int wireless_status; | 211 | int wireless_status; |
248 | bool have_rsts; | 212 | bool have_rsts; |
249 | int lcd_state; | ||
250 | 213 | ||
251 | struct rfkill *gps_rfkill; | 214 | struct rfkill *gps_rfkill; |
252 | 215 | ||
@@ -559,48 +522,6 @@ error: | |||
559 | /* | 522 | /* |
560 | * Backlight device | 523 | * Backlight device |
561 | */ | 524 | */ |
562 | static int asus_lcd_status(struct asus_laptop *asus) | ||
563 | { | ||
564 | return asus->lcd_state; | ||
565 | } | ||
566 | |||
567 | static int asus_lcd_set(struct asus_laptop *asus, int value) | ||
568 | { | ||
569 | int lcd = 0; | ||
570 | acpi_status status = 0; | ||
571 | |||
572 | lcd = !!value; | ||
573 | |||
574 | if (lcd == asus_lcd_status(asus)) | ||
575 | return 0; | ||
576 | |||
577 | if (!lcd_switch_handle) | ||
578 | return -ENODEV; | ||
579 | |||
580 | status = acpi_evaluate_object(lcd_switch_handle, | ||
581 | NULL, NULL, NULL); | ||
582 | |||
583 | if (ACPI_FAILURE(status)) { | ||
584 | pr_warning("Error switching LCD\n"); | ||
585 | return -ENODEV; | ||
586 | } | ||
587 | |||
588 | asus->lcd_state = lcd; | ||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | static void lcd_blank(struct asus_laptop *asus, int blank) | ||
593 | { | ||
594 | struct backlight_device *bd = asus->backlight_device; | ||
595 | |||
596 | asus->lcd_state = (blank == FB_BLANK_UNBLANK); | ||
597 | |||
598 | if (bd) { | ||
599 | bd->props.power = blank; | ||
600 | backlight_update_status(bd); | ||
601 | } | ||
602 | } | ||
603 | |||
604 | static int asus_read_brightness(struct backlight_device *bd) | 525 | static int asus_read_brightness(struct backlight_device *bd) |
605 | { | 526 | { |
606 | struct asus_laptop *asus = bl_get_data(bd); | 527 | struct asus_laptop *asus = bl_get_data(bd); |
@@ -628,16 +549,9 @@ static int asus_set_brightness(struct backlight_device *bd, int value) | |||
628 | 549 | ||
629 | static int update_bl_status(struct backlight_device *bd) | 550 | static int update_bl_status(struct backlight_device *bd) |
630 | { | 551 | { |
631 | struct asus_laptop *asus = bl_get_data(bd); | ||
632 | int rv; | ||
633 | int value = bd->props.brightness; | 552 | int value = bd->props.brightness; |
634 | 553 | ||
635 | rv = asus_set_brightness(bd, value); | 554 | return asus_set_brightness(bd, value); |
636 | if (rv) | ||
637 | return rv; | ||
638 | |||
639 | value = (bd->props.power == FB_BLANK_UNBLANK) ? 1 : 0; | ||
640 | return asus_lcd_set(asus, value); | ||
641 | } | 555 | } |
642 | 556 | ||
643 | static const struct backlight_ops asusbl_ops = { | 557 | static const struct backlight_ops asusbl_ops = { |
@@ -661,8 +575,7 @@ static int asus_backlight_init(struct asus_laptop *asus) | |||
661 | struct backlight_properties props; | 575 | struct backlight_properties props; |
662 | 576 | ||
663 | if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) || | 577 | if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) || |
664 | acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL) || | 578 | acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL)) |
665 | !lcd_switch_handle) | ||
666 | return 0; | 579 | return 0; |
667 | 580 | ||
668 | memset(&props, 0, sizeof(struct backlight_properties)); | 581 | memset(&props, 0, sizeof(struct backlight_properties)); |
@@ -971,41 +884,6 @@ static void asus_set_display(struct asus_laptop *asus, int value) | |||
971 | return; | 884 | return; |
972 | } | 885 | } |
973 | 886 | ||
974 | static int read_display(struct asus_laptop *asus) | ||
975 | { | ||
976 | unsigned long long value = 0; | ||
977 | acpi_status rv = AE_OK; | ||
978 | |||
979 | /* | ||
980 | * In most of the case, we know how to set the display, but sometime | ||
981 | * we can't read it | ||
982 | */ | ||
983 | if (display_get_handle) { | ||
984 | rv = acpi_evaluate_integer(display_get_handle, NULL, | ||
985 | NULL, &value); | ||
986 | if (ACPI_FAILURE(rv)) | ||
987 | pr_warning("Error reading display status\n"); | ||
988 | } | ||
989 | |||
990 | value &= 0x0F; /* needed for some models, shouldn't hurt others */ | ||
991 | |||
992 | return value; | ||
993 | } | ||
994 | |||
995 | /* | ||
996 | * Now, *this* one could be more user-friendly, but so far, no-one has | ||
997 | * complained. The significance of bits is the same as in store_disp() | ||
998 | */ | ||
999 | static ssize_t show_disp(struct device *dev, | ||
1000 | struct device_attribute *attr, char *buf) | ||
1001 | { | ||
1002 | struct asus_laptop *asus = dev_get_drvdata(dev); | ||
1003 | |||
1004 | if (!display_get_handle) | ||
1005 | return -ENODEV; | ||
1006 | return sprintf(buf, "%d\n", read_display(asus)); | ||
1007 | } | ||
1008 | |||
1009 | /* | 887 | /* |
1010 | * Experimental support for display switching. As of now: 1 should activate | 888 | * Experimental support for display switching. As of now: 1 should activate |
1011 | * the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI. | 889 | * the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI. |
@@ -1247,15 +1125,6 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event) | |||
1247 | struct asus_laptop *asus = acpi_driver_data(device); | 1125 | struct asus_laptop *asus = acpi_driver_data(device); |
1248 | u16 count; | 1126 | u16 count; |
1249 | 1127 | ||
1250 | /* | ||
1251 | * We need to tell the backlight device when the backlight power is | ||
1252 | * switched | ||
1253 | */ | ||
1254 | if (event == ATKD_LCD_ON) | ||
1255 | lcd_blank(asus, FB_BLANK_UNBLANK); | ||
1256 | else if (event == ATKD_LCD_OFF) | ||
1257 | lcd_blank(asus, FB_BLANK_POWERDOWN); | ||
1258 | |||
1259 | /* TODO Find a better way to handle events count. */ | 1128 | /* TODO Find a better way to handle events count. */ |
1260 | count = asus->event_count[event % 128]++; | 1129 | count = asus->event_count[event % 128]++; |
1261 | acpi_bus_generate_proc_event(asus->device, event, count); | 1130 | acpi_bus_generate_proc_event(asus->device, event, count); |
@@ -1282,7 +1151,7 @@ static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR, | |||
1282 | show_bluetooth, store_bluetooth); | 1151 | show_bluetooth, store_bluetooth); |
1283 | static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax); | 1152 | static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax); |
1284 | static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan); | 1153 | static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan); |
1285 | static DEVICE_ATTR(display, S_IRUGO | S_IWUSR, show_disp, store_disp); | 1154 | static DEVICE_ATTR(display, S_IWUSR, NULL, store_disp); |
1286 | static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd); | 1155 | static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd); |
1287 | static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl); | 1156 | static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl); |
1288 | static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw); | 1157 | static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw); |
@@ -1393,26 +1262,6 @@ static struct platform_driver platform_driver = { | |||
1393 | } | 1262 | } |
1394 | }; | 1263 | }; |
1395 | 1264 | ||
1396 | static int asus_handle_init(char *name, acpi_handle * handle, | ||
1397 | char **paths, int num_paths) | ||
1398 | { | ||
1399 | int i; | ||
1400 | acpi_status status; | ||
1401 | |||
1402 | for (i = 0; i < num_paths; i++) { | ||
1403 | status = acpi_get_handle(NULL, paths[i], handle); | ||
1404 | if (ACPI_SUCCESS(status)) | ||
1405 | return 0; | ||
1406 | } | ||
1407 | |||
1408 | *handle = NULL; | ||
1409 | return -ENODEV; | ||
1410 | } | ||
1411 | |||
1412 | #define ASUS_HANDLE_INIT(object) \ | ||
1413 | asus_handle_init(#object, &object##_handle, object##_paths, \ | ||
1414 | ARRAY_SIZE(object##_paths)) | ||
1415 | |||
1416 | /* | 1265 | /* |
1417 | * This function is used to initialize the context with right values. In this | 1266 | * This function is used to initialize the context with right values. In this |
1418 | * method, we can make all the detection we want, and modify the asus_laptop | 1267 | * method, we can make all the detection we want, and modify the asus_laptop |
@@ -1498,10 +1347,6 @@ static int asus_laptop_get_info(struct asus_laptop *asus) | |||
1498 | if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL)) | 1347 | if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL)) |
1499 | asus->have_rsts = true; | 1348 | asus->have_rsts = true; |
1500 | 1349 | ||
1501 | /* Scheduled for removal */ | ||
1502 | ASUS_HANDLE_INIT(lcd_switch); | ||
1503 | ASUS_HANDLE_INIT(display_get); | ||
1504 | |||
1505 | kfree(model); | 1350 | kfree(model); |
1506 | 1351 | ||
1507 | return AE_OK; | 1352 | return AE_OK; |
@@ -1553,10 +1398,23 @@ static int __devinit asus_acpi_init(struct asus_laptop *asus) | |||
1553 | asus_als_level(asus, asus->light_level); | 1398 | asus_als_level(asus, asus->light_level); |
1554 | } | 1399 | } |
1555 | 1400 | ||
1556 | asus->lcd_state = 1; /* LCD should be on when the module load */ | ||
1557 | return result; | 1401 | return result; |
1558 | } | 1402 | } |
1559 | 1403 | ||
1404 | static void __devinit asus_dmi_check(void) | ||
1405 | { | ||
1406 | const char *model; | ||
1407 | |||
1408 | model = dmi_get_system_info(DMI_PRODUCT_NAME); | ||
1409 | if (!model) | ||
1410 | return; | ||
1411 | |||
1412 | /* On L1400B WLED control the sound card, don't mess with it ... */ | ||
1413 | if (strncmp(model, "L1400B", 6) == 0) { | ||
1414 | wlan_status = -1; | ||
1415 | } | ||
1416 | } | ||
1417 | |||
1560 | static bool asus_device_present; | 1418 | static bool asus_device_present; |
1561 | 1419 | ||
1562 | static int __devinit asus_acpi_add(struct acpi_device *device) | 1420 | static int __devinit asus_acpi_add(struct acpi_device *device) |
@@ -1575,6 +1433,8 @@ static int __devinit asus_acpi_add(struct acpi_device *device) | |||
1575 | device->driver_data = asus; | 1433 | device->driver_data = asus; |
1576 | asus->device = device; | 1434 | asus->device = device; |
1577 | 1435 | ||
1436 | asus_dmi_check(); | ||
1437 | |||
1578 | result = asus_acpi_init(asus); | 1438 | result = asus_acpi_init(asus); |
1579 | if (result) | 1439 | if (result) |
1580 | goto fail_platform; | 1440 | goto fail_platform; |
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c new file mode 100644 index 000000000000..0580d99b0798 --- /dev/null +++ b/drivers/platform/x86/asus-nb-wmi.c | |||
@@ -0,0 +1,98 @@ | |||
1 | /* | ||
2 | * Asus Notebooks WMI hotkey driver | ||
3 | * | ||
4 | * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
19 | */ | ||
20 | |||
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
22 | |||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/input.h> | ||
27 | #include <linux/input/sparse-keymap.h> | ||
28 | |||
29 | #include "asus-wmi.h" | ||
30 | |||
31 | #define ASUS_NB_WMI_FILE "asus-nb-wmi" | ||
32 | |||
33 | MODULE_AUTHOR("Corentin Chary <corentincj@iksaif.net>"); | ||
34 | MODULE_DESCRIPTION("Asus Notebooks WMI Hotkey Driver"); | ||
35 | MODULE_LICENSE("GPL"); | ||
36 | |||
37 | #define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" | ||
38 | |||
39 | MODULE_ALIAS("wmi:"ASUS_NB_WMI_EVENT_GUID); | ||
40 | |||
41 | static const struct key_entry asus_nb_wmi_keymap[] = { | ||
42 | { KE_KEY, 0x30, { KEY_VOLUMEUP } }, | ||
43 | { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, | ||
44 | { KE_KEY, 0x32, { KEY_MUTE } }, | ||
45 | { KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */ | ||
46 | { KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */ | ||
47 | { KE_KEY, 0x40, { KEY_PREVIOUSSONG } }, | ||
48 | { KE_KEY, 0x41, { KEY_NEXTSONG } }, | ||
49 | { KE_KEY, 0x43, { KEY_STOPCD } }, | ||
50 | { KE_KEY, 0x45, { KEY_PLAYPAUSE } }, | ||
51 | { KE_KEY, 0x4c, { KEY_MEDIA } }, | ||
52 | { KE_KEY, 0x50, { KEY_EMAIL } }, | ||
53 | { KE_KEY, 0x51, { KEY_WWW } }, | ||
54 | { KE_KEY, 0x55, { KEY_CALC } }, | ||
55 | { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */ | ||
56 | { KE_KEY, 0x5D, { KEY_WLAN } }, | ||
57 | { KE_KEY, 0x5E, { KEY_WLAN } }, | ||
58 | { KE_KEY, 0x5F, { KEY_WLAN } }, | ||
59 | { KE_KEY, 0x60, { KEY_SWITCHVIDEOMODE } }, | ||
60 | { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, | ||
61 | { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, | ||
62 | { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, | ||
63 | { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, | ||
64 | { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, | ||
65 | { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, | ||
66 | { KE_KEY, 0x82, { KEY_CAMERA } }, | ||
67 | { KE_KEY, 0x88, { KEY_RFKILL } }, | ||
68 | { KE_KEY, 0x8A, { KEY_PROG1 } }, | ||
69 | { KE_KEY, 0x95, { KEY_MEDIA } }, | ||
70 | { KE_KEY, 0x99, { KEY_PHONE } }, | ||
71 | { KE_KEY, 0xb5, { KEY_CALC } }, | ||
72 | { KE_KEY, 0xc4, { KEY_KBDILLUMUP } }, | ||
73 | { KE_KEY, 0xc5, { KEY_KBDILLUMDOWN } }, | ||
74 | { KE_END, 0}, | ||
75 | }; | ||
76 | |||
77 | static struct asus_wmi_driver asus_nb_wmi_driver = { | ||
78 | .name = ASUS_NB_WMI_FILE, | ||
79 | .owner = THIS_MODULE, | ||
80 | .event_guid = ASUS_NB_WMI_EVENT_GUID, | ||
81 | .keymap = asus_nb_wmi_keymap, | ||
82 | .input_name = "Asus WMI hotkeys", | ||
83 | .input_phys = ASUS_NB_WMI_FILE "/input0", | ||
84 | }; | ||
85 | |||
86 | |||
87 | static int __init asus_nb_wmi_init(void) | ||
88 | { | ||
89 | return asus_wmi_register_driver(&asus_nb_wmi_driver); | ||
90 | } | ||
91 | |||
92 | static void __exit asus_nb_wmi_exit(void) | ||
93 | { | ||
94 | asus_wmi_unregister_driver(&asus_nb_wmi_driver); | ||
95 | } | ||
96 | |||
97 | module_init(asus_nb_wmi_init); | ||
98 | module_exit(asus_nb_wmi_exit); | ||
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c new file mode 100644 index 000000000000..efc776cb0c66 --- /dev/null +++ b/drivers/platform/x86/asus-wmi.c | |||
@@ -0,0 +1,1656 @@ | |||
1 | /* | ||
2 | * Asus PC WMI hotkey driver | ||
3 | * | ||
4 | * Copyright(C) 2010 Intel Corporation. | ||
5 | * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> | ||
6 | * | ||
7 | * Portions based on wistron_btns.c: | ||
8 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> | ||
9 | * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> | ||
10 | * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
28 | |||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/module.h> | ||
31 | #include <linux/init.h> | ||
32 | #include <linux/types.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/input.h> | ||
35 | #include <linux/input/sparse-keymap.h> | ||
36 | #include <linux/fb.h> | ||
37 | #include <linux/backlight.h> | ||
38 | #include <linux/leds.h> | ||
39 | #include <linux/rfkill.h> | ||
40 | #include <linux/pci.h> | ||
41 | #include <linux/pci_hotplug.h> | ||
42 | #include <linux/hwmon.h> | ||
43 | #include <linux/hwmon-sysfs.h> | ||
44 | #include <linux/debugfs.h> | ||
45 | #include <linux/seq_file.h> | ||
46 | #include <linux/platform_device.h> | ||
47 | #include <acpi/acpi_bus.h> | ||
48 | #include <acpi/acpi_drivers.h> | ||
49 | |||
50 | #include "asus-wmi.h" | ||
51 | |||
52 | MODULE_AUTHOR("Corentin Chary <corentincj@iksaif.net>, " | ||
53 | "Yong Wang <yong.y.wang@intel.com>"); | ||
54 | MODULE_DESCRIPTION("Asus Generic WMI Driver"); | ||
55 | MODULE_LICENSE("GPL"); | ||
56 | |||
57 | #define to_platform_driver(drv) \ | ||
58 | (container_of((drv), struct platform_driver, driver)) | ||
59 | |||
60 | #define to_asus_wmi_driver(pdrv) \ | ||
61 | (container_of((pdrv), struct asus_wmi_driver, platform_driver)) | ||
62 | |||
63 | #define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" | ||
64 | |||
65 | #define NOTIFY_BRNUP_MIN 0x11 | ||
66 | #define NOTIFY_BRNUP_MAX 0x1f | ||
67 | #define NOTIFY_BRNDOWN_MIN 0x20 | ||
68 | #define NOTIFY_BRNDOWN_MAX 0x2e | ||
69 | |||
70 | /* WMI Methods */ | ||
71 | #define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */ | ||
72 | #define ASUS_WMI_METHODID_SFBD 0x44424653 /* Set First Boot Device */ | ||
73 | #define ASUS_WMI_METHODID_GLCD 0x44434C47 /* Get LCD status */ | ||
74 | #define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */ | ||
75 | #define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */ | ||
76 | #define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */ | ||
77 | #define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */ | ||
78 | #define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */ | ||
79 | #define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */ | ||
80 | #define ASUS_WMI_METHODID_DEVP 0x50564544 /* DEVice Policy */ | ||
81 | #define ASUS_WMI_METHODID_OSVR 0x5256534F /* OS VeRsion */ | ||
82 | #define ASUS_WMI_METHODID_DSTS 0x53544344 /* Device STatuS */ | ||
83 | #define ASUS_WMI_METHODID_DSTS2 0x53545344 /* Device STatuS #2*/ | ||
84 | #define ASUS_WMI_METHODID_BSTS 0x53545342 /* Bios STatuS ? */ | ||
85 | #define ASUS_WMI_METHODID_DEVS 0x53564544 /* DEVice Set */ | ||
86 | #define ASUS_WMI_METHODID_CFVS 0x53564643 /* CPU Frequency Volt Set */ | ||
87 | #define ASUS_WMI_METHODID_KBFT 0x5446424B /* KeyBoard FilTer */ | ||
88 | #define ASUS_WMI_METHODID_INIT 0x54494E49 /* INITialize */ | ||
89 | #define ASUS_WMI_METHODID_HKEY 0x59454B48 /* Hot KEY ?? */ | ||
90 | |||
91 | #define ASUS_WMI_UNSUPPORTED_METHOD 0xFFFFFFFE | ||
92 | |||
93 | /* Wireless */ | ||
94 | #define ASUS_WMI_DEVID_HW_SWITCH 0x00010001 | ||
95 | #define ASUS_WMI_DEVID_WIRELESS_LED 0x00010002 | ||
96 | #define ASUS_WMI_DEVID_WLAN 0x00010011 | ||
97 | #define ASUS_WMI_DEVID_BLUETOOTH 0x00010013 | ||
98 | #define ASUS_WMI_DEVID_GPS 0x00010015 | ||
99 | #define ASUS_WMI_DEVID_WIMAX 0x00010017 | ||
100 | #define ASUS_WMI_DEVID_WWAN3G 0x00010019 | ||
101 | #define ASUS_WMI_DEVID_UWB 0x00010021 | ||
102 | |||
103 | /* Leds */ | ||
104 | /* 0x000200XX and 0x000400XX */ | ||
105 | |||
106 | /* Backlight and Brightness */ | ||
107 | #define ASUS_WMI_DEVID_BACKLIGHT 0x00050011 | ||
108 | #define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012 | ||
109 | #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 | ||
110 | #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ | ||
111 | |||
112 | /* Misc */ | ||
113 | #define ASUS_WMI_DEVID_CAMERA 0x00060013 | ||
114 | |||
115 | /* Storage */ | ||
116 | #define ASUS_WMI_DEVID_CARDREADER 0x00080013 | ||
117 | |||
118 | /* Input */ | ||
119 | #define ASUS_WMI_DEVID_TOUCHPAD 0x00100011 | ||
120 | #define ASUS_WMI_DEVID_TOUCHPAD_LED 0x00100012 | ||
121 | |||
122 | /* Fan, Thermal */ | ||
123 | #define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011 | ||
124 | #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 | ||
125 | |||
126 | /* Power */ | ||
127 | #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 | ||
128 | |||
129 | /* DSTS masks */ | ||
130 | #define ASUS_WMI_DSTS_STATUS_BIT 0x00000001 | ||
131 | #define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002 | ||
132 | #define ASUS_WMI_DSTS_PRESENCE_BIT 0x00010000 | ||
133 | #define ASUS_WMI_DSTS_USER_BIT 0x00020000 | ||
134 | #define ASUS_WMI_DSTS_BIOS_BIT 0x00040000 | ||
135 | #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF | ||
136 | #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 | ||
137 | |||
138 | struct bios_args { | ||
139 | u32 arg0; | ||
140 | u32 arg1; | ||
141 | } __packed; | ||
142 | |||
143 | /* | ||
144 | * <platform>/ - debugfs root directory | ||
145 | * dev_id - current dev_id | ||
146 | * ctrl_param - current ctrl_param | ||
147 | * method_id - current method_id | ||
148 | * devs - call DEVS(dev_id, ctrl_param) and print result | ||
149 | * dsts - call DSTS(dev_id) and print result | ||
150 | * call - call method_id(dev_id, ctrl_param) and print result | ||
151 | */ | ||
152 | struct asus_wmi_debug { | ||
153 | struct dentry *root; | ||
154 | u32 method_id; | ||
155 | u32 dev_id; | ||
156 | u32 ctrl_param; | ||
157 | }; | ||
158 | |||
159 | struct asus_rfkill { | ||
160 | struct asus_wmi *asus; | ||
161 | struct rfkill *rfkill; | ||
162 | u32 dev_id; | ||
163 | }; | ||
164 | |||
165 | struct asus_wmi { | ||
166 | int dsts_id; | ||
167 | int spec; | ||
168 | int sfun; | ||
169 | |||
170 | struct input_dev *inputdev; | ||
171 | struct backlight_device *backlight_device; | ||
172 | struct device *hwmon_device; | ||
173 | struct platform_device *platform_device; | ||
174 | |||
175 | struct led_classdev tpd_led; | ||
176 | int tpd_led_wk; | ||
177 | struct workqueue_struct *led_workqueue; | ||
178 | struct work_struct tpd_led_work; | ||
179 | |||
180 | struct asus_rfkill wlan; | ||
181 | struct asus_rfkill bluetooth; | ||
182 | struct asus_rfkill wimax; | ||
183 | struct asus_rfkill wwan3g; | ||
184 | |||
185 | struct hotplug_slot *hotplug_slot; | ||
186 | struct mutex hotplug_lock; | ||
187 | struct mutex wmi_lock; | ||
188 | struct workqueue_struct *hotplug_workqueue; | ||
189 | struct work_struct hotplug_work; | ||
190 | |||
191 | struct asus_wmi_debug debug; | ||
192 | |||
193 | struct asus_wmi_driver *driver; | ||
194 | }; | ||
195 | |||
196 | static int asus_wmi_input_init(struct asus_wmi *asus) | ||
197 | { | ||
198 | int err; | ||
199 | |||
200 | asus->inputdev = input_allocate_device(); | ||
201 | if (!asus->inputdev) | ||
202 | return -ENOMEM; | ||
203 | |||
204 | asus->inputdev->name = asus->driver->input_phys; | ||
205 | asus->inputdev->phys = asus->driver->input_name; | ||
206 | asus->inputdev->id.bustype = BUS_HOST; | ||
207 | asus->inputdev->dev.parent = &asus->platform_device->dev; | ||
208 | |||
209 | err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL); | ||
210 | if (err) | ||
211 | goto err_free_dev; | ||
212 | |||
213 | err = input_register_device(asus->inputdev); | ||
214 | if (err) | ||
215 | goto err_free_keymap; | ||
216 | |||
217 | return 0; | ||
218 | |||
219 | err_free_keymap: | ||
220 | sparse_keymap_free(asus->inputdev); | ||
221 | err_free_dev: | ||
222 | input_free_device(asus->inputdev); | ||
223 | return err; | ||
224 | } | ||
225 | |||
226 | static void asus_wmi_input_exit(struct asus_wmi *asus) | ||
227 | { | ||
228 | if (asus->inputdev) { | ||
229 | sparse_keymap_free(asus->inputdev); | ||
230 | input_unregister_device(asus->inputdev); | ||
231 | } | ||
232 | |||
233 | asus->inputdev = NULL; | ||
234 | } | ||
235 | |||
236 | static int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, | ||
237 | u32 *retval) | ||
238 | { | ||
239 | struct bios_args args = { | ||
240 | .arg0 = arg0, | ||
241 | .arg1 = arg1, | ||
242 | }; | ||
243 | struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; | ||
244 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
245 | acpi_status status; | ||
246 | union acpi_object *obj; | ||
247 | u32 tmp; | ||
248 | |||
249 | status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 1, method_id, | ||
250 | &input, &output); | ||
251 | |||
252 | if (ACPI_FAILURE(status)) | ||
253 | goto exit; | ||
254 | |||
255 | obj = (union acpi_object *)output.pointer; | ||
256 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
257 | tmp = (u32) obj->integer.value; | ||
258 | else | ||
259 | tmp = 0; | ||
260 | |||
261 | if (retval) | ||
262 | *retval = tmp; | ||
263 | |||
264 | kfree(obj); | ||
265 | |||
266 | exit: | ||
267 | if (ACPI_FAILURE(status)) | ||
268 | return -EIO; | ||
269 | |||
270 | if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) | ||
271 | return -ENODEV; | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) | ||
277 | { | ||
278 | return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval); | ||
279 | } | ||
280 | |||
281 | static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, | ||
282 | u32 *retval) | ||
283 | { | ||
284 | return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id, | ||
285 | ctrl_param, retval); | ||
286 | } | ||
287 | |||
288 | /* Helper for special devices with magic return codes */ | ||
289 | static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, | ||
290 | u32 dev_id, u32 mask) | ||
291 | { | ||
292 | u32 retval = 0; | ||
293 | int err; | ||
294 | |||
295 | err = asus_wmi_get_devstate(asus, dev_id, &retval); | ||
296 | |||
297 | if (err < 0) | ||
298 | return err; | ||
299 | |||
300 | if (!(retval & ASUS_WMI_DSTS_PRESENCE_BIT)) | ||
301 | return -ENODEV; | ||
302 | |||
303 | if (mask == ASUS_WMI_DSTS_STATUS_BIT) { | ||
304 | if (retval & ASUS_WMI_DSTS_UNKNOWN_BIT) | ||
305 | return -ENODEV; | ||
306 | } | ||
307 | |||
308 | return retval & mask; | ||
309 | } | ||
310 | |||
311 | static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id) | ||
312 | { | ||
313 | return asus_wmi_get_devstate_bits(asus, dev_id, | ||
314 | ASUS_WMI_DSTS_STATUS_BIT); | ||
315 | } | ||
316 | |||
317 | /* | ||
318 | * LEDs | ||
319 | */ | ||
320 | /* | ||
321 | * These functions actually update the LED's, and are called from a | ||
322 | * workqueue. By doing this as separate work rather than when the LED | ||
323 | * subsystem asks, we avoid messing with the Asus ACPI stuff during a | ||
324 | * potentially bad time, such as a timer interrupt. | ||
325 | */ | ||
326 | static void tpd_led_update(struct work_struct *work) | ||
327 | { | ||
328 | int ctrl_param; | ||
329 | struct asus_wmi *asus; | ||
330 | |||
331 | asus = container_of(work, struct asus_wmi, tpd_led_work); | ||
332 | |||
333 | ctrl_param = asus->tpd_led_wk; | ||
334 | asus_wmi_set_devstate(ASUS_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL); | ||
335 | } | ||
336 | |||
337 | static void tpd_led_set(struct led_classdev *led_cdev, | ||
338 | enum led_brightness value) | ||
339 | { | ||
340 | struct asus_wmi *asus; | ||
341 | |||
342 | asus = container_of(led_cdev, struct asus_wmi, tpd_led); | ||
343 | |||
344 | asus->tpd_led_wk = !!value; | ||
345 | queue_work(asus->led_workqueue, &asus->tpd_led_work); | ||
346 | } | ||
347 | |||
348 | static int read_tpd_led_state(struct asus_wmi *asus) | ||
349 | { | ||
350 | return asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_TOUCHPAD_LED); | ||
351 | } | ||
352 | |||
353 | static enum led_brightness tpd_led_get(struct led_classdev *led_cdev) | ||
354 | { | ||
355 | struct asus_wmi *asus; | ||
356 | |||
357 | asus = container_of(led_cdev, struct asus_wmi, tpd_led); | ||
358 | |||
359 | return read_tpd_led_state(asus); | ||
360 | } | ||
361 | |||
362 | static int asus_wmi_led_init(struct asus_wmi *asus) | ||
363 | { | ||
364 | int rv; | ||
365 | |||
366 | if (read_tpd_led_state(asus) < 0) | ||
367 | return 0; | ||
368 | |||
369 | asus->led_workqueue = create_singlethread_workqueue("led_workqueue"); | ||
370 | if (!asus->led_workqueue) | ||
371 | return -ENOMEM; | ||
372 | INIT_WORK(&asus->tpd_led_work, tpd_led_update); | ||
373 | |||
374 | asus->tpd_led.name = "asus::touchpad"; | ||
375 | asus->tpd_led.brightness_set = tpd_led_set; | ||
376 | asus->tpd_led.brightness_get = tpd_led_get; | ||
377 | asus->tpd_led.max_brightness = 1; | ||
378 | |||
379 | rv = led_classdev_register(&asus->platform_device->dev, &asus->tpd_led); | ||
380 | if (rv) { | ||
381 | destroy_workqueue(asus->led_workqueue); | ||
382 | return rv; | ||
383 | } | ||
384 | |||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | static void asus_wmi_led_exit(struct asus_wmi *asus) | ||
389 | { | ||
390 | if (asus->tpd_led.dev) | ||
391 | led_classdev_unregister(&asus->tpd_led); | ||
392 | if (asus->led_workqueue) | ||
393 | destroy_workqueue(asus->led_workqueue); | ||
394 | } | ||
395 | |||
396 | /* | ||
397 | * PCI hotplug (for wlan rfkill) | ||
398 | */ | ||
399 | static bool asus_wlan_rfkill_blocked(struct asus_wmi *asus) | ||
400 | { | ||
401 | int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); | ||
402 | |||
403 | if (result < 0) | ||
404 | return false; | ||
405 | return !result; | ||
406 | } | ||
407 | |||
408 | static void asus_rfkill_hotplug(struct asus_wmi *asus) | ||
409 | { | ||
410 | struct pci_dev *dev; | ||
411 | struct pci_bus *bus; | ||
412 | bool blocked; | ||
413 | bool absent; | ||
414 | u32 l; | ||
415 | |||
416 | mutex_lock(&asus->wmi_lock); | ||
417 | blocked = asus_wlan_rfkill_blocked(asus); | ||
418 | mutex_unlock(&asus->wmi_lock); | ||
419 | |||
420 | mutex_lock(&asus->hotplug_lock); | ||
421 | |||
422 | if (asus->wlan.rfkill) | ||
423 | rfkill_set_sw_state(asus->wlan.rfkill, blocked); | ||
424 | |||
425 | if (asus->hotplug_slot) { | ||
426 | bus = pci_find_bus(0, 1); | ||
427 | if (!bus) { | ||
428 | pr_warning("Unable to find PCI bus 1?\n"); | ||
429 | goto out_unlock; | ||
430 | } | ||
431 | |||
432 | if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) { | ||
433 | pr_err("Unable to read PCI config space?\n"); | ||
434 | goto out_unlock; | ||
435 | } | ||
436 | absent = (l == 0xffffffff); | ||
437 | |||
438 | if (blocked != absent) { | ||
439 | pr_warning("BIOS says wireless lan is %s, " | ||
440 | "but the pci device is %s\n", | ||
441 | blocked ? "blocked" : "unblocked", | ||
442 | absent ? "absent" : "present"); | ||
443 | pr_warning("skipped wireless hotplug as probably " | ||
444 | "inappropriate for this model\n"); | ||
445 | goto out_unlock; | ||
446 | } | ||
447 | |||
448 | if (!blocked) { | ||
449 | dev = pci_get_slot(bus, 0); | ||
450 | if (dev) { | ||
451 | /* Device already present */ | ||
452 | pci_dev_put(dev); | ||
453 | goto out_unlock; | ||
454 | } | ||
455 | dev = pci_scan_single_device(bus, 0); | ||
456 | if (dev) { | ||
457 | pci_bus_assign_resources(bus); | ||
458 | if (pci_bus_add_device(dev)) | ||
459 | pr_err("Unable to hotplug wifi\n"); | ||
460 | } | ||
461 | } else { | ||
462 | dev = pci_get_slot(bus, 0); | ||
463 | if (dev) { | ||
464 | pci_remove_bus_device(dev); | ||
465 | pci_dev_put(dev); | ||
466 | } | ||
467 | } | ||
468 | } | ||
469 | |||
470 | out_unlock: | ||
471 | mutex_unlock(&asus->hotplug_lock); | ||
472 | } | ||
473 | |||
474 | static void asus_rfkill_notify(acpi_handle handle, u32 event, void *data) | ||
475 | { | ||
476 | struct asus_wmi *asus = data; | ||
477 | |||
478 | if (event != ACPI_NOTIFY_BUS_CHECK) | ||
479 | return; | ||
480 | |||
481 | /* | ||
482 | * We can't call directly asus_rfkill_hotplug because most | ||
483 | * of the time WMBC is still being executed and not reetrant. | ||
484 | * There is currently no way to tell ACPICA that we want this | ||
485 | * method to be serialized, we schedule a asus_rfkill_hotplug | ||
486 | * call later, in a safer context. | ||
487 | */ | ||
488 | queue_work(asus->hotplug_workqueue, &asus->hotplug_work); | ||
489 | } | ||
490 | |||
491 | static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node) | ||
492 | { | ||
493 | acpi_status status; | ||
494 | acpi_handle handle; | ||
495 | |||
496 | status = acpi_get_handle(NULL, node, &handle); | ||
497 | |||
498 | if (ACPI_SUCCESS(status)) { | ||
499 | status = acpi_install_notify_handler(handle, | ||
500 | ACPI_SYSTEM_NOTIFY, | ||
501 | asus_rfkill_notify, asus); | ||
502 | if (ACPI_FAILURE(status)) | ||
503 | pr_warning("Failed to register notify on %s\n", node); | ||
504 | } else | ||
505 | return -ENODEV; | ||
506 | |||
507 | return 0; | ||
508 | } | ||
509 | |||
510 | static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node) | ||
511 | { | ||
512 | acpi_status status = AE_OK; | ||
513 | acpi_handle handle; | ||
514 | |||
515 | status = acpi_get_handle(NULL, node, &handle); | ||
516 | |||
517 | if (ACPI_SUCCESS(status)) { | ||
518 | status = acpi_remove_notify_handler(handle, | ||
519 | ACPI_SYSTEM_NOTIFY, | ||
520 | asus_rfkill_notify); | ||
521 | if (ACPI_FAILURE(status)) | ||
522 | pr_err("Error removing rfkill notify handler %s\n", | ||
523 | node); | ||
524 | } | ||
525 | } | ||
526 | |||
527 | static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot, | ||
528 | u8 *value) | ||
529 | { | ||
530 | struct asus_wmi *asus = hotplug_slot->private; | ||
531 | int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); | ||
532 | |||
533 | if (result < 0) | ||
534 | return result; | ||
535 | |||
536 | *value = !!result; | ||
537 | return 0; | ||
538 | } | ||
539 | |||
540 | static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) | ||
541 | { | ||
542 | kfree(hotplug_slot->info); | ||
543 | kfree(hotplug_slot); | ||
544 | } | ||
545 | |||
546 | static struct hotplug_slot_ops asus_hotplug_slot_ops = { | ||
547 | .owner = THIS_MODULE, | ||
548 | .get_adapter_status = asus_get_adapter_status, | ||
549 | .get_power_status = asus_get_adapter_status, | ||
550 | }; | ||
551 | |||
552 | static void asus_hotplug_work(struct work_struct *work) | ||
553 | { | ||
554 | struct asus_wmi *asus; | ||
555 | |||
556 | asus = container_of(work, struct asus_wmi, hotplug_work); | ||
557 | asus_rfkill_hotplug(asus); | ||
558 | } | ||
559 | |||
560 | static int asus_setup_pci_hotplug(struct asus_wmi *asus) | ||
561 | { | ||
562 | int ret = -ENOMEM; | ||
563 | struct pci_bus *bus = pci_find_bus(0, 1); | ||
564 | |||
565 | if (!bus) { | ||
566 | pr_err("Unable to find wifi PCI bus\n"); | ||
567 | return -ENODEV; | ||
568 | } | ||
569 | |||
570 | asus->hotplug_workqueue = | ||
571 | create_singlethread_workqueue("hotplug_workqueue"); | ||
572 | if (!asus->hotplug_workqueue) | ||
573 | goto error_workqueue; | ||
574 | |||
575 | INIT_WORK(&asus->hotplug_work, asus_hotplug_work); | ||
576 | |||
577 | asus->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); | ||
578 | if (!asus->hotplug_slot) | ||
579 | goto error_slot; | ||
580 | |||
581 | asus->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), | ||
582 | GFP_KERNEL); | ||
583 | if (!asus->hotplug_slot->info) | ||
584 | goto error_info; | ||
585 | |||
586 | asus->hotplug_slot->private = asus; | ||
587 | asus->hotplug_slot->release = &asus_cleanup_pci_hotplug; | ||
588 | asus->hotplug_slot->ops = &asus_hotplug_slot_ops; | ||
589 | asus_get_adapter_status(asus->hotplug_slot, | ||
590 | &asus->hotplug_slot->info->adapter_status); | ||
591 | |||
592 | ret = pci_hp_register(asus->hotplug_slot, bus, 0, "asus-wifi"); | ||
593 | if (ret) { | ||
594 | pr_err("Unable to register hotplug slot - %d\n", ret); | ||
595 | goto error_register; | ||
596 | } | ||
597 | |||
598 | return 0; | ||
599 | |||
600 | error_register: | ||
601 | kfree(asus->hotplug_slot->info); | ||
602 | error_info: | ||
603 | kfree(asus->hotplug_slot); | ||
604 | asus->hotplug_slot = NULL; | ||
605 | error_slot: | ||
606 | destroy_workqueue(asus->hotplug_workqueue); | ||
607 | error_workqueue: | ||
608 | return ret; | ||
609 | } | ||
610 | |||
611 | /* | ||
612 | * Rfkill devices | ||
613 | */ | ||
614 | static int asus_rfkill_set(void *data, bool blocked) | ||
615 | { | ||
616 | struct asus_rfkill *priv = data; | ||
617 | u32 ctrl_param = !blocked; | ||
618 | |||
619 | return asus_wmi_set_devstate(priv->dev_id, ctrl_param, NULL); | ||
620 | } | ||
621 | |||
622 | static void asus_rfkill_query(struct rfkill *rfkill, void *data) | ||
623 | { | ||
624 | struct asus_rfkill *priv = data; | ||
625 | int result; | ||
626 | |||
627 | result = asus_wmi_get_devstate_simple(priv->asus, priv->dev_id); | ||
628 | |||
629 | if (result < 0) | ||
630 | return; | ||
631 | |||
632 | rfkill_set_sw_state(priv->rfkill, !result); | ||
633 | } | ||
634 | |||
635 | static int asus_rfkill_wlan_set(void *data, bool blocked) | ||
636 | { | ||
637 | struct asus_rfkill *priv = data; | ||
638 | struct asus_wmi *asus = priv->asus; | ||
639 | int ret; | ||
640 | |||
641 | /* | ||
642 | * This handler is enabled only if hotplug is enabled. | ||
643 | * In this case, the asus_wmi_set_devstate() will | ||
644 | * trigger a wmi notification and we need to wait | ||
645 | * this call to finish before being able to call | ||
646 | * any wmi method | ||
647 | */ | ||
648 | mutex_lock(&asus->wmi_lock); | ||
649 | ret = asus_rfkill_set(data, blocked); | ||
650 | mutex_unlock(&asus->wmi_lock); | ||
651 | return ret; | ||
652 | } | ||
653 | |||
654 | static const struct rfkill_ops asus_rfkill_wlan_ops = { | ||
655 | .set_block = asus_rfkill_wlan_set, | ||
656 | .query = asus_rfkill_query, | ||
657 | }; | ||
658 | |||
659 | static const struct rfkill_ops asus_rfkill_ops = { | ||
660 | .set_block = asus_rfkill_set, | ||
661 | .query = asus_rfkill_query, | ||
662 | }; | ||
663 | |||
664 | static int asus_new_rfkill(struct asus_wmi *asus, | ||
665 | struct asus_rfkill *arfkill, | ||
666 | const char *name, enum rfkill_type type, int dev_id) | ||
667 | { | ||
668 | int result = asus_wmi_get_devstate_simple(asus, dev_id); | ||
669 | struct rfkill **rfkill = &arfkill->rfkill; | ||
670 | |||
671 | if (result < 0) | ||
672 | return result; | ||
673 | |||
674 | arfkill->dev_id = dev_id; | ||
675 | arfkill->asus = asus; | ||
676 | |||
677 | if (dev_id == ASUS_WMI_DEVID_WLAN && asus->driver->hotplug_wireless) | ||
678 | *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, | ||
679 | &asus_rfkill_wlan_ops, arfkill); | ||
680 | else | ||
681 | *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type, | ||
682 | &asus_rfkill_ops, arfkill); | ||
683 | |||
684 | if (!*rfkill) | ||
685 | return -EINVAL; | ||
686 | |||
687 | rfkill_init_sw_state(*rfkill, !result); | ||
688 | result = rfkill_register(*rfkill); | ||
689 | if (result) { | ||
690 | rfkill_destroy(*rfkill); | ||
691 | *rfkill = NULL; | ||
692 | return result; | ||
693 | } | ||
694 | return 0; | ||
695 | } | ||
696 | |||
697 | static void asus_wmi_rfkill_exit(struct asus_wmi *asus) | ||
698 | { | ||
699 | asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P5"); | ||
700 | asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P6"); | ||
701 | asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P7"); | ||
702 | if (asus->wlan.rfkill) { | ||
703 | rfkill_unregister(asus->wlan.rfkill); | ||
704 | rfkill_destroy(asus->wlan.rfkill); | ||
705 | asus->wlan.rfkill = NULL; | ||
706 | } | ||
707 | /* | ||
708 | * Refresh pci hotplug in case the rfkill state was changed after | ||
709 | * asus_unregister_rfkill_notifier() | ||
710 | */ | ||
711 | asus_rfkill_hotplug(asus); | ||
712 | if (asus->hotplug_slot) | ||
713 | pci_hp_deregister(asus->hotplug_slot); | ||
714 | if (asus->hotplug_workqueue) | ||
715 | destroy_workqueue(asus->hotplug_workqueue); | ||
716 | |||
717 | if (asus->bluetooth.rfkill) { | ||
718 | rfkill_unregister(asus->bluetooth.rfkill); | ||
719 | rfkill_destroy(asus->bluetooth.rfkill); | ||
720 | asus->bluetooth.rfkill = NULL; | ||
721 | } | ||
722 | if (asus->wimax.rfkill) { | ||
723 | rfkill_unregister(asus->wimax.rfkill); | ||
724 | rfkill_destroy(asus->wimax.rfkill); | ||
725 | asus->wimax.rfkill = NULL; | ||
726 | } | ||
727 | if (asus->wwan3g.rfkill) { | ||
728 | rfkill_unregister(asus->wwan3g.rfkill); | ||
729 | rfkill_destroy(asus->wwan3g.rfkill); | ||
730 | asus->wwan3g.rfkill = NULL; | ||
731 | } | ||
732 | } | ||
733 | |||
734 | static int asus_wmi_rfkill_init(struct asus_wmi *asus) | ||
735 | { | ||
736 | int result = 0; | ||
737 | |||
738 | mutex_init(&asus->hotplug_lock); | ||
739 | mutex_init(&asus->wmi_lock); | ||
740 | |||
741 | result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan", | ||
742 | RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN); | ||
743 | |||
744 | if (result && result != -ENODEV) | ||
745 | goto exit; | ||
746 | |||
747 | result = asus_new_rfkill(asus, &asus->bluetooth, | ||
748 | "asus-bluetooth", RFKILL_TYPE_BLUETOOTH, | ||
749 | ASUS_WMI_DEVID_BLUETOOTH); | ||
750 | |||
751 | if (result && result != -ENODEV) | ||
752 | goto exit; | ||
753 | |||
754 | result = asus_new_rfkill(asus, &asus->wimax, "asus-wimax", | ||
755 | RFKILL_TYPE_WIMAX, ASUS_WMI_DEVID_WIMAX); | ||
756 | |||
757 | if (result && result != -ENODEV) | ||
758 | goto exit; | ||
759 | |||
760 | result = asus_new_rfkill(asus, &asus->wwan3g, "asus-wwan3g", | ||
761 | RFKILL_TYPE_WWAN, ASUS_WMI_DEVID_WWAN3G); | ||
762 | |||
763 | if (result && result != -ENODEV) | ||
764 | goto exit; | ||
765 | |||
766 | if (!asus->driver->hotplug_wireless) | ||
767 | goto exit; | ||
768 | |||
769 | result = asus_setup_pci_hotplug(asus); | ||
770 | /* | ||
771 | * If we get -EBUSY then something else is handling the PCI hotplug - | ||
772 | * don't fail in this case | ||
773 | */ | ||
774 | if (result == -EBUSY) | ||
775 | result = 0; | ||
776 | |||
777 | asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P5"); | ||
778 | asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P6"); | ||
779 | asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P7"); | ||
780 | /* | ||
781 | * Refresh pci hotplug in case the rfkill state was changed during | ||
782 | * setup. | ||
783 | */ | ||
784 | asus_rfkill_hotplug(asus); | ||
785 | |||
786 | exit: | ||
787 | if (result && result != -ENODEV) | ||
788 | asus_wmi_rfkill_exit(asus); | ||
789 | |||
790 | if (result == -ENODEV) | ||
791 | result = 0; | ||
792 | |||
793 | return result; | ||
794 | } | ||
795 | |||
796 | /* | ||
797 | * Hwmon device | ||
798 | */ | ||
799 | static ssize_t asus_hwmon_pwm1(struct device *dev, | ||
800 | struct device_attribute *attr, | ||
801 | char *buf) | ||
802 | { | ||
803 | struct asus_wmi *asus = dev_get_drvdata(dev); | ||
804 | u32 value; | ||
805 | int err; | ||
806 | |||
807 | err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); | ||
808 | |||
809 | if (err < 0) | ||
810 | return err; | ||
811 | |||
812 | value |= 0xFF; | ||
813 | |||
814 | if (value == 1) /* Low Speed */ | ||
815 | value = 85; | ||
816 | else if (value == 2) | ||
817 | value = 170; | ||
818 | else if (value == 3) | ||
819 | value = 255; | ||
820 | else if (value != 0) { | ||
821 | pr_err("Unknown fan speed %#x", value); | ||
822 | value = -1; | ||
823 | } | ||
824 | |||
825 | return sprintf(buf, "%d\n", value); | ||
826 | } | ||
827 | |||
828 | static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL, 0); | ||
829 | |||
830 | static ssize_t | ||
831 | show_name(struct device *dev, struct device_attribute *attr, char *buf) | ||
832 | { | ||
833 | return sprintf(buf, "asus\n"); | ||
834 | } | ||
835 | static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); | ||
836 | |||
837 | static struct attribute *hwmon_attributes[] = { | ||
838 | &sensor_dev_attr_pwm1.dev_attr.attr, | ||
839 | &sensor_dev_attr_name.dev_attr.attr, | ||
840 | NULL | ||
841 | }; | ||
842 | |||
843 | static mode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, | ||
844 | struct attribute *attr, int idx) | ||
845 | { | ||
846 | struct device *dev = container_of(kobj, struct device, kobj); | ||
847 | struct platform_device *pdev = to_platform_device(dev->parent); | ||
848 | struct asus_wmi *asus = platform_get_drvdata(pdev); | ||
849 | bool ok = true; | ||
850 | int dev_id = -1; | ||
851 | u32 value = ASUS_WMI_UNSUPPORTED_METHOD; | ||
852 | |||
853 | if (attr == &sensor_dev_attr_pwm1.dev_attr.attr) | ||
854 | dev_id = ASUS_WMI_DEVID_FAN_CTRL; | ||
855 | |||
856 | if (dev_id != -1) { | ||
857 | int err = asus_wmi_get_devstate(asus, dev_id, &value); | ||
858 | |||
859 | if (err < 0) | ||
860 | return err; | ||
861 | } | ||
862 | |||
863 | if (dev_id == ASUS_WMI_DEVID_FAN_CTRL) { | ||
864 | /* | ||
865 | * We need to find a better way, probably using sfun, | ||
866 | * bits or spec ... | ||
867 | * Currently we disable it if: | ||
868 | * - ASUS_WMI_UNSUPPORTED_METHOD is returned | ||
869 | * - reverved bits are non-zero | ||
870 | * - sfun and presence bit are not set | ||
871 | */ | ||
872 | if (value != ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 | ||
873 | || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))) | ||
874 | ok = false; | ||
875 | } | ||
876 | |||
877 | return ok ? attr->mode : 0; | ||
878 | } | ||
879 | |||
880 | static struct attribute_group hwmon_attribute_group = { | ||
881 | .is_visible = asus_hwmon_sysfs_is_visible, | ||
882 | .attrs = hwmon_attributes | ||
883 | }; | ||
884 | |||
885 | static void asus_wmi_hwmon_exit(struct asus_wmi *asus) | ||
886 | { | ||
887 | struct device *hwmon; | ||
888 | |||
889 | hwmon = asus->hwmon_device; | ||
890 | if (!hwmon) | ||
891 | return; | ||
892 | sysfs_remove_group(&hwmon->kobj, &hwmon_attribute_group); | ||
893 | hwmon_device_unregister(hwmon); | ||
894 | asus->hwmon_device = NULL; | ||
895 | } | ||
896 | |||
897 | static int asus_wmi_hwmon_init(struct asus_wmi *asus) | ||
898 | { | ||
899 | struct device *hwmon; | ||
900 | int result; | ||
901 | |||
902 | hwmon = hwmon_device_register(&asus->platform_device->dev); | ||
903 | if (IS_ERR(hwmon)) { | ||
904 | pr_err("Could not register asus hwmon device\n"); | ||
905 | return PTR_ERR(hwmon); | ||
906 | } | ||
907 | asus->hwmon_device = hwmon; | ||
908 | result = sysfs_create_group(&hwmon->kobj, &hwmon_attribute_group); | ||
909 | if (result) | ||
910 | asus_wmi_hwmon_exit(asus); | ||
911 | return result; | ||
912 | } | ||
913 | |||
914 | /* | ||
915 | * Backlight | ||
916 | */ | ||
917 | static int read_backlight_power(struct asus_wmi *asus) | ||
918 | { | ||
919 | int ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_BACKLIGHT); | ||
920 | |||
921 | if (ret < 0) | ||
922 | return ret; | ||
923 | |||
924 | return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; | ||
925 | } | ||
926 | |||
927 | static int read_brightness_max(struct asus_wmi *asus) | ||
928 | { | ||
929 | u32 retval; | ||
930 | int err; | ||
931 | |||
932 | err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); | ||
933 | |||
934 | if (err < 0) | ||
935 | return err; | ||
936 | |||
937 | retval = retval & ASUS_WMI_DSTS_MAX_BRIGTH_MASK; | ||
938 | retval >>= 8; | ||
939 | |||
940 | if (!retval) | ||
941 | return -ENODEV; | ||
942 | |||
943 | return retval; | ||
944 | } | ||
945 | |||
946 | static int read_brightness(struct backlight_device *bd) | ||
947 | { | ||
948 | struct asus_wmi *asus = bl_get_data(bd); | ||
949 | u32 retval; | ||
950 | int err; | ||
951 | |||
952 | err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); | ||
953 | |||
954 | if (err < 0) | ||
955 | return err; | ||
956 | |||
957 | return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK; | ||
958 | } | ||
959 | |||
960 | static int update_bl_status(struct backlight_device *bd) | ||
961 | { | ||
962 | struct asus_wmi *asus = bl_get_data(bd); | ||
963 | u32 ctrl_param; | ||
964 | int power, err; | ||
965 | |||
966 | ctrl_param = bd->props.brightness; | ||
967 | |||
968 | err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS, | ||
969 | ctrl_param, NULL); | ||
970 | |||
971 | if (err < 0) | ||
972 | return err; | ||
973 | |||
974 | power = read_backlight_power(asus); | ||
975 | if (power != -ENODEV && bd->props.power != power) { | ||
976 | ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK); | ||
977 | err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, | ||
978 | ctrl_param, NULL); | ||
979 | } | ||
980 | return err; | ||
981 | } | ||
982 | |||
983 | static const struct backlight_ops asus_wmi_bl_ops = { | ||
984 | .get_brightness = read_brightness, | ||
985 | .update_status = update_bl_status, | ||
986 | }; | ||
987 | |||
988 | static int asus_wmi_backlight_notify(struct asus_wmi *asus, int code) | ||
989 | { | ||
990 | struct backlight_device *bd = asus->backlight_device; | ||
991 | int old = bd->props.brightness; | ||
992 | int new = old; | ||
993 | |||
994 | if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) | ||
995 | new = code - NOTIFY_BRNUP_MIN + 1; | ||
996 | else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) | ||
997 | new = code - NOTIFY_BRNDOWN_MIN; | ||
998 | |||
999 | bd->props.brightness = new; | ||
1000 | backlight_update_status(bd); | ||
1001 | backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); | ||
1002 | |||
1003 | return old; | ||
1004 | } | ||
1005 | |||
1006 | static int asus_wmi_backlight_init(struct asus_wmi *asus) | ||
1007 | { | ||
1008 | struct backlight_device *bd; | ||
1009 | struct backlight_properties props; | ||
1010 | int max; | ||
1011 | int power; | ||
1012 | |||
1013 | max = read_brightness_max(asus); | ||
1014 | |||
1015 | if (max == -ENODEV) | ||
1016 | max = 0; | ||
1017 | else if (max < 0) | ||
1018 | return max; | ||
1019 | |||
1020 | power = read_backlight_power(asus); | ||
1021 | |||
1022 | if (power == -ENODEV) | ||
1023 | power = FB_BLANK_UNBLANK; | ||
1024 | else if (power < 0) | ||
1025 | return power; | ||
1026 | |||
1027 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
1028 | props.max_brightness = max; | ||
1029 | bd = backlight_device_register(asus->driver->name, | ||
1030 | &asus->platform_device->dev, asus, | ||
1031 | &asus_wmi_bl_ops, &props); | ||
1032 | if (IS_ERR(bd)) { | ||
1033 | pr_err("Could not register backlight device\n"); | ||
1034 | return PTR_ERR(bd); | ||
1035 | } | ||
1036 | |||
1037 | asus->backlight_device = bd; | ||
1038 | |||
1039 | bd->props.brightness = read_brightness(bd); | ||
1040 | bd->props.power = power; | ||
1041 | backlight_update_status(bd); | ||
1042 | |||
1043 | return 0; | ||
1044 | } | ||
1045 | |||
1046 | static void asus_wmi_backlight_exit(struct asus_wmi *asus) | ||
1047 | { | ||
1048 | if (asus->backlight_device) | ||
1049 | backlight_device_unregister(asus->backlight_device); | ||
1050 | |||
1051 | asus->backlight_device = NULL; | ||
1052 | } | ||
1053 | |||
1054 | static void asus_wmi_notify(u32 value, void *context) | ||
1055 | { | ||
1056 | struct asus_wmi *asus = context; | ||
1057 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
1058 | union acpi_object *obj; | ||
1059 | acpi_status status; | ||
1060 | int code; | ||
1061 | int orig_code; | ||
1062 | |||
1063 | status = wmi_get_event_data(value, &response); | ||
1064 | if (status != AE_OK) { | ||
1065 | pr_err("bad event status 0x%x\n", status); | ||
1066 | return; | ||
1067 | } | ||
1068 | |||
1069 | obj = (union acpi_object *)response.pointer; | ||
1070 | |||
1071 | if (!obj || obj->type != ACPI_TYPE_INTEGER) | ||
1072 | goto exit; | ||
1073 | |||
1074 | code = obj->integer.value; | ||
1075 | orig_code = code; | ||
1076 | |||
1077 | if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) | ||
1078 | code = NOTIFY_BRNUP_MIN; | ||
1079 | else if (code >= NOTIFY_BRNDOWN_MIN && | ||
1080 | code <= NOTIFY_BRNDOWN_MAX) | ||
1081 | code = NOTIFY_BRNDOWN_MIN; | ||
1082 | |||
1083 | if (code == NOTIFY_BRNUP_MIN || code == NOTIFY_BRNDOWN_MIN) { | ||
1084 | if (!acpi_video_backlight_support()) | ||
1085 | asus_wmi_backlight_notify(asus, orig_code); | ||
1086 | } else if (!sparse_keymap_report_event(asus->inputdev, code, 1, true)) | ||
1087 | pr_info("Unknown key %x pressed\n", code); | ||
1088 | |||
1089 | exit: | ||
1090 | kfree(obj); | ||
1091 | } | ||
1092 | |||
1093 | /* | ||
1094 | * Sys helpers | ||
1095 | */ | ||
1096 | static int parse_arg(const char *buf, unsigned long count, int *val) | ||
1097 | { | ||
1098 | if (!count) | ||
1099 | return 0; | ||
1100 | if (sscanf(buf, "%i", val) != 1) | ||
1101 | return -EINVAL; | ||
1102 | return count; | ||
1103 | } | ||
1104 | |||
1105 | static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid, | ||
1106 | const char *buf, size_t count) | ||
1107 | { | ||
1108 | u32 retval; | ||
1109 | int rv, err, value; | ||
1110 | |||
1111 | value = asus_wmi_get_devstate_simple(asus, devid); | ||
1112 | if (value == -ENODEV) /* Check device presence */ | ||
1113 | return value; | ||
1114 | |||
1115 | rv = parse_arg(buf, count, &value); | ||
1116 | err = asus_wmi_set_devstate(devid, value, &retval); | ||
1117 | |||
1118 | if (err < 0) | ||
1119 | return err; | ||
1120 | |||
1121 | return rv; | ||
1122 | } | ||
1123 | |||
1124 | static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf) | ||
1125 | { | ||
1126 | int value = asus_wmi_get_devstate_simple(asus, devid); | ||
1127 | |||
1128 | if (value < 0) | ||
1129 | return value; | ||
1130 | |||
1131 | return sprintf(buf, "%d\n", value); | ||
1132 | } | ||
1133 | |||
1134 | #define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm) \ | ||
1135 | static ssize_t show_##_name(struct device *dev, \ | ||
1136 | struct device_attribute *attr, \ | ||
1137 | char *buf) \ | ||
1138 | { \ | ||
1139 | struct asus_wmi *asus = dev_get_drvdata(dev); \ | ||
1140 | \ | ||
1141 | return show_sys_wmi(asus, _cm, buf); \ | ||
1142 | } \ | ||
1143 | static ssize_t store_##_name(struct device *dev, \ | ||
1144 | struct device_attribute *attr, \ | ||
1145 | const char *buf, size_t count) \ | ||
1146 | { \ | ||
1147 | struct asus_wmi *asus = dev_get_drvdata(dev); \ | ||
1148 | \ | ||
1149 | return store_sys_wmi(asus, _cm, buf, count); \ | ||
1150 | } \ | ||
1151 | static struct device_attribute dev_attr_##_name = { \ | ||
1152 | .attr = { \ | ||
1153 | .name = __stringify(_name), \ | ||
1154 | .mode = _mode }, \ | ||
1155 | .show = show_##_name, \ | ||
1156 | .store = store_##_name, \ | ||
1157 | } | ||
1158 | |||
1159 | ASUS_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, ASUS_WMI_DEVID_TOUCHPAD); | ||
1160 | ASUS_WMI_CREATE_DEVICE_ATTR(camera, 0644, ASUS_WMI_DEVID_CAMERA); | ||
1161 | ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER); | ||
1162 | |||
1163 | static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, | ||
1164 | const char *buf, size_t count) | ||
1165 | { | ||
1166 | int value; | ||
1167 | |||
1168 | if (!count || sscanf(buf, "%i", &value) != 1) | ||
1169 | return -EINVAL; | ||
1170 | if (value < 0 || value > 2) | ||
1171 | return -EINVAL; | ||
1172 | |||
1173 | return asus_wmi_evaluate_method(ASUS_WMI_METHODID_CFVS, value, 0, NULL); | ||
1174 | } | ||
1175 | |||
1176 | static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv); | ||
1177 | |||
1178 | static struct attribute *platform_attributes[] = { | ||
1179 | &dev_attr_cpufv.attr, | ||
1180 | &dev_attr_camera.attr, | ||
1181 | &dev_attr_cardr.attr, | ||
1182 | &dev_attr_touchpad.attr, | ||
1183 | NULL | ||
1184 | }; | ||
1185 | |||
1186 | static mode_t asus_sysfs_is_visible(struct kobject *kobj, | ||
1187 | struct attribute *attr, int idx) | ||
1188 | { | ||
1189 | struct device *dev = container_of(kobj, struct device, kobj); | ||
1190 | struct platform_device *pdev = to_platform_device(dev); | ||
1191 | struct asus_wmi *asus = platform_get_drvdata(pdev); | ||
1192 | bool ok = true; | ||
1193 | int devid = -1; | ||
1194 | |||
1195 | if (attr == &dev_attr_camera.attr) | ||
1196 | devid = ASUS_WMI_DEVID_CAMERA; | ||
1197 | else if (attr == &dev_attr_cardr.attr) | ||
1198 | devid = ASUS_WMI_DEVID_CARDREADER; | ||
1199 | else if (attr == &dev_attr_touchpad.attr) | ||
1200 | devid = ASUS_WMI_DEVID_TOUCHPAD; | ||
1201 | |||
1202 | if (devid != -1) | ||
1203 | ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); | ||
1204 | |||
1205 | return ok ? attr->mode : 0; | ||
1206 | } | ||
1207 | |||
1208 | static struct attribute_group platform_attribute_group = { | ||
1209 | .is_visible = asus_sysfs_is_visible, | ||
1210 | .attrs = platform_attributes | ||
1211 | }; | ||
1212 | |||
1213 | static void asus_wmi_sysfs_exit(struct platform_device *device) | ||
1214 | { | ||
1215 | sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); | ||
1216 | } | ||
1217 | |||
1218 | static int asus_wmi_sysfs_init(struct platform_device *device) | ||
1219 | { | ||
1220 | return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); | ||
1221 | } | ||
1222 | |||
1223 | /* | ||
1224 | * Platform device | ||
1225 | */ | ||
1226 | static int __init asus_wmi_platform_init(struct asus_wmi *asus) | ||
1227 | { | ||
1228 | int rv; | ||
1229 | |||
1230 | /* INIT enable hotkeys on some models */ | ||
1231 | if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_INIT, 0, 0, &rv)) | ||
1232 | pr_info("Initialization: %#x", rv); | ||
1233 | |||
1234 | /* We don't know yet what to do with this version... */ | ||
1235 | if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SPEC, 0, 0x9, &rv)) { | ||
1236 | pr_info("BIOS WMI version: %d.%d", rv >> 8, rv & 0xFF); | ||
1237 | asus->spec = rv; | ||
1238 | } | ||
1239 | |||
1240 | /* | ||
1241 | * The SFUN method probably allows the original driver to get the list | ||
1242 | * of features supported by a given model. For now, 0x0100 or 0x0800 | ||
1243 | * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card. | ||
1244 | * The significance of others is yet to be found. | ||
1245 | */ | ||
1246 | if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SFUN, 0, 0, &rv)) { | ||
1247 | pr_info("SFUN value: %#x", rv); | ||
1248 | asus->sfun = rv; | ||
1249 | } | ||
1250 | |||
1251 | /* | ||
1252 | * Eee PC and Notebooks seems to have different method_id for DSTS, | ||
1253 | * but it may also be related to the BIOS's SPEC. | ||
1254 | * Note, on most Eeepc, there is no way to check if a method exist | ||
1255 | * or note, while on notebooks, they returns 0xFFFFFFFE on failure, | ||
1256 | * but once again, SPEC may probably be used for that kind of things. | ||
1257 | */ | ||
1258 | if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL)) | ||
1259 | asus->dsts_id = ASUS_WMI_METHODID_DSTS; | ||
1260 | else if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS2, 0, 0, NULL)) | ||
1261 | asus->dsts_id = ASUS_WMI_METHODID_DSTS2; | ||
1262 | |||
1263 | if (!asus->dsts_id) { | ||
1264 | pr_err("Can't find DSTS"); | ||
1265 | return -ENODEV; | ||
1266 | } | ||
1267 | |||
1268 | return asus_wmi_sysfs_init(asus->platform_device); | ||
1269 | } | ||
1270 | |||
1271 | static void asus_wmi_platform_exit(struct asus_wmi *asus) | ||
1272 | { | ||
1273 | asus_wmi_sysfs_exit(asus->platform_device); | ||
1274 | } | ||
1275 | |||
1276 | /* | ||
1277 | * debugfs | ||
1278 | */ | ||
1279 | struct asus_wmi_debugfs_node { | ||
1280 | struct asus_wmi *asus; | ||
1281 | char *name; | ||
1282 | int (*show) (struct seq_file *m, void *data); | ||
1283 | }; | ||
1284 | |||
1285 | static int show_dsts(struct seq_file *m, void *data) | ||
1286 | { | ||
1287 | struct asus_wmi *asus = m->private; | ||
1288 | int err; | ||
1289 | u32 retval = -1; | ||
1290 | |||
1291 | err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval); | ||
1292 | |||
1293 | if (err < 0) | ||
1294 | return err; | ||
1295 | |||
1296 | seq_printf(m, "DSTS(%#x) = %#x\n", asus->debug.dev_id, retval); | ||
1297 | |||
1298 | return 0; | ||
1299 | } | ||
1300 | |||
1301 | static int show_devs(struct seq_file *m, void *data) | ||
1302 | { | ||
1303 | struct asus_wmi *asus = m->private; | ||
1304 | int err; | ||
1305 | u32 retval = -1; | ||
1306 | |||
1307 | err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param, | ||
1308 | &retval); | ||
1309 | |||
1310 | if (err < 0) | ||
1311 | return err; | ||
1312 | |||
1313 | seq_printf(m, "DEVS(%#x, %#x) = %#x\n", asus->debug.dev_id, | ||
1314 | asus->debug.ctrl_param, retval); | ||
1315 | |||
1316 | return 0; | ||
1317 | } | ||
1318 | |||
1319 | static int show_call(struct seq_file *m, void *data) | ||
1320 | { | ||
1321 | struct asus_wmi *asus = m->private; | ||
1322 | struct bios_args args = { | ||
1323 | .arg0 = asus->debug.dev_id, | ||
1324 | .arg1 = asus->debug.ctrl_param, | ||
1325 | }; | ||
1326 | struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; | ||
1327 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
1328 | union acpi_object *obj; | ||
1329 | acpi_status status; | ||
1330 | |||
1331 | status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, | ||
1332 | 1, asus->debug.method_id, | ||
1333 | &input, &output); | ||
1334 | |||
1335 | if (ACPI_FAILURE(status)) | ||
1336 | return -EIO; | ||
1337 | |||
1338 | obj = (union acpi_object *)output.pointer; | ||
1339 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
1340 | seq_printf(m, "%#x(%#x, %#x) = %#x\n", asus->debug.method_id, | ||
1341 | asus->debug.dev_id, asus->debug.ctrl_param, | ||
1342 | (u32) obj->integer.value); | ||
1343 | else | ||
1344 | seq_printf(m, "%#x(%#x, %#x) = t:%d\n", asus->debug.method_id, | ||
1345 | asus->debug.dev_id, asus->debug.ctrl_param, | ||
1346 | obj ? obj->type : -1); | ||
1347 | |||
1348 | kfree(obj); | ||
1349 | |||
1350 | return 0; | ||
1351 | } | ||
1352 | |||
1353 | static struct asus_wmi_debugfs_node asus_wmi_debug_files[] = { | ||
1354 | {NULL, "devs", show_devs}, | ||
1355 | {NULL, "dsts", show_dsts}, | ||
1356 | {NULL, "call", show_call}, | ||
1357 | }; | ||
1358 | |||
1359 | static int asus_wmi_debugfs_open(struct inode *inode, struct file *file) | ||
1360 | { | ||
1361 | struct asus_wmi_debugfs_node *node = inode->i_private; | ||
1362 | |||
1363 | return single_open(file, node->show, node->asus); | ||
1364 | } | ||
1365 | |||
1366 | static const struct file_operations asus_wmi_debugfs_io_ops = { | ||
1367 | .owner = THIS_MODULE, | ||
1368 | .open = asus_wmi_debugfs_open, | ||
1369 | .read = seq_read, | ||
1370 | .llseek = seq_lseek, | ||
1371 | .release = single_release, | ||
1372 | }; | ||
1373 | |||
1374 | static void asus_wmi_debugfs_exit(struct asus_wmi *asus) | ||
1375 | { | ||
1376 | debugfs_remove_recursive(asus->debug.root); | ||
1377 | } | ||
1378 | |||
1379 | static int asus_wmi_debugfs_init(struct asus_wmi *asus) | ||
1380 | { | ||
1381 | struct dentry *dent; | ||
1382 | int i; | ||
1383 | |||
1384 | asus->debug.root = debugfs_create_dir(asus->driver->name, NULL); | ||
1385 | if (!asus->debug.root) { | ||
1386 | pr_err("failed to create debugfs directory"); | ||
1387 | goto error_debugfs; | ||
1388 | } | ||
1389 | |||
1390 | dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, | ||
1391 | asus->debug.root, &asus->debug.method_id); | ||
1392 | if (!dent) | ||
1393 | goto error_debugfs; | ||
1394 | |||
1395 | dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, | ||
1396 | asus->debug.root, &asus->debug.dev_id); | ||
1397 | if (!dent) | ||
1398 | goto error_debugfs; | ||
1399 | |||
1400 | dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, | ||
1401 | asus->debug.root, &asus->debug.ctrl_param); | ||
1402 | if (!dent) | ||
1403 | goto error_debugfs; | ||
1404 | |||
1405 | for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) { | ||
1406 | struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i]; | ||
1407 | |||
1408 | node->asus = asus; | ||
1409 | dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, | ||
1410 | asus->debug.root, node, | ||
1411 | &asus_wmi_debugfs_io_ops); | ||
1412 | if (!dent) { | ||
1413 | pr_err("failed to create debug file: %s\n", node->name); | ||
1414 | goto error_debugfs; | ||
1415 | } | ||
1416 | } | ||
1417 | |||
1418 | return 0; | ||
1419 | |||
1420 | error_debugfs: | ||
1421 | asus_wmi_debugfs_exit(asus); | ||
1422 | return -ENOMEM; | ||
1423 | } | ||
1424 | |||
1425 | /* | ||
1426 | * WMI Driver | ||
1427 | */ | ||
1428 | static int asus_wmi_add(struct platform_device *pdev) | ||
1429 | { | ||
1430 | struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); | ||
1431 | struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv); | ||
1432 | struct asus_wmi *asus; | ||
1433 | acpi_status status; | ||
1434 | int err; | ||
1435 | |||
1436 | asus = kzalloc(sizeof(struct asus_wmi), GFP_KERNEL); | ||
1437 | if (!asus) | ||
1438 | return -ENOMEM; | ||
1439 | |||
1440 | asus->driver = wdrv; | ||
1441 | asus->platform_device = pdev; | ||
1442 | wdrv->platform_device = pdev; | ||
1443 | platform_set_drvdata(asus->platform_device, asus); | ||
1444 | |||
1445 | if (wdrv->quirks) | ||
1446 | wdrv->quirks(asus->driver); | ||
1447 | |||
1448 | err = asus_wmi_platform_init(asus); | ||
1449 | if (err) | ||
1450 | goto fail_platform; | ||
1451 | |||
1452 | err = asus_wmi_input_init(asus); | ||
1453 | if (err) | ||
1454 | goto fail_input; | ||
1455 | |||
1456 | err = asus_wmi_hwmon_init(asus); | ||
1457 | if (err) | ||
1458 | goto fail_hwmon; | ||
1459 | |||
1460 | err = asus_wmi_led_init(asus); | ||
1461 | if (err) | ||
1462 | goto fail_leds; | ||
1463 | |||
1464 | err = asus_wmi_rfkill_init(asus); | ||
1465 | if (err) | ||
1466 | goto fail_rfkill; | ||
1467 | |||
1468 | if (!acpi_video_backlight_support()) { | ||
1469 | err = asus_wmi_backlight_init(asus); | ||
1470 | if (err && err != -ENODEV) | ||
1471 | goto fail_backlight; | ||
1472 | } else | ||
1473 | pr_info("Backlight controlled by ACPI video driver\n"); | ||
1474 | |||
1475 | status = wmi_install_notify_handler(asus->driver->event_guid, | ||
1476 | asus_wmi_notify, asus); | ||
1477 | if (ACPI_FAILURE(status)) { | ||
1478 | pr_err("Unable to register notify handler - %d\n", status); | ||
1479 | err = -ENODEV; | ||
1480 | goto fail_wmi_handler; | ||
1481 | } | ||
1482 | |||
1483 | err = asus_wmi_debugfs_init(asus); | ||
1484 | if (err) | ||
1485 | goto fail_debugfs; | ||
1486 | |||
1487 | return 0; | ||
1488 | |||
1489 | fail_debugfs: | ||
1490 | wmi_remove_notify_handler(asus->driver->event_guid); | ||
1491 | fail_wmi_handler: | ||
1492 | asus_wmi_backlight_exit(asus); | ||
1493 | fail_backlight: | ||
1494 | asus_wmi_rfkill_exit(asus); | ||
1495 | fail_rfkill: | ||
1496 | asus_wmi_led_exit(asus); | ||
1497 | fail_leds: | ||
1498 | asus_wmi_hwmon_exit(asus); | ||
1499 | fail_hwmon: | ||
1500 | asus_wmi_input_exit(asus); | ||
1501 | fail_input: | ||
1502 | asus_wmi_platform_exit(asus); | ||
1503 | fail_platform: | ||
1504 | kfree(asus); | ||
1505 | return err; | ||
1506 | } | ||
1507 | |||
1508 | static int asus_wmi_remove(struct platform_device *device) | ||
1509 | { | ||
1510 | struct asus_wmi *asus; | ||
1511 | |||
1512 | asus = platform_get_drvdata(device); | ||
1513 | wmi_remove_notify_handler(asus->driver->event_guid); | ||
1514 | asus_wmi_backlight_exit(asus); | ||
1515 | asus_wmi_input_exit(asus); | ||
1516 | asus_wmi_hwmon_exit(asus); | ||
1517 | asus_wmi_led_exit(asus); | ||
1518 | asus_wmi_rfkill_exit(asus); | ||
1519 | asus_wmi_debugfs_exit(asus); | ||
1520 | asus_wmi_platform_exit(asus); | ||
1521 | |||
1522 | kfree(asus); | ||
1523 | return 0; | ||
1524 | } | ||
1525 | |||
1526 | /* | ||
1527 | * Platform driver - hibernate/resume callbacks | ||
1528 | */ | ||
1529 | static int asus_hotk_thaw(struct device *device) | ||
1530 | { | ||
1531 | struct asus_wmi *asus = dev_get_drvdata(device); | ||
1532 | |||
1533 | if (asus->wlan.rfkill) { | ||
1534 | bool wlan; | ||
1535 | |||
1536 | /* | ||
1537 | * Work around bios bug - acpi _PTS turns off the wireless led | ||
1538 | * during suspend. Normally it restores it on resume, but | ||
1539 | * we should kick it ourselves in case hibernation is aborted. | ||
1540 | */ | ||
1541 | wlan = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN); | ||
1542 | asus_wmi_set_devstate(ASUS_WMI_DEVID_WLAN, wlan, NULL); | ||
1543 | } | ||
1544 | |||
1545 | return 0; | ||
1546 | } | ||
1547 | |||
1548 | static int asus_hotk_restore(struct device *device) | ||
1549 | { | ||
1550 | struct asus_wmi *asus = dev_get_drvdata(device); | ||
1551 | int bl; | ||
1552 | |||
1553 | /* Refresh both wlan rfkill state and pci hotplug */ | ||
1554 | if (asus->wlan.rfkill) | ||
1555 | asus_rfkill_hotplug(asus); | ||
1556 | |||
1557 | if (asus->bluetooth.rfkill) { | ||
1558 | bl = !asus_wmi_get_devstate_simple(asus, | ||
1559 | ASUS_WMI_DEVID_BLUETOOTH); | ||
1560 | rfkill_set_sw_state(asus->bluetooth.rfkill, bl); | ||
1561 | } | ||
1562 | if (asus->wimax.rfkill) { | ||
1563 | bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WIMAX); | ||
1564 | rfkill_set_sw_state(asus->wimax.rfkill, bl); | ||
1565 | } | ||
1566 | if (asus->wwan3g.rfkill) { | ||
1567 | bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WWAN3G); | ||
1568 | rfkill_set_sw_state(asus->wwan3g.rfkill, bl); | ||
1569 | } | ||
1570 | |||
1571 | return 0; | ||
1572 | } | ||
1573 | |||
1574 | static const struct dev_pm_ops asus_pm_ops = { | ||
1575 | .thaw = asus_hotk_thaw, | ||
1576 | .restore = asus_hotk_restore, | ||
1577 | }; | ||
1578 | |||
1579 | static int asus_wmi_probe(struct platform_device *pdev) | ||
1580 | { | ||
1581 | struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); | ||
1582 | struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv); | ||
1583 | int ret; | ||
1584 | |||
1585 | if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) { | ||
1586 | pr_warning("Management GUID not found\n"); | ||
1587 | return -ENODEV; | ||
1588 | } | ||
1589 | |||
1590 | if (wdrv->event_guid && !wmi_has_guid(wdrv->event_guid)) { | ||
1591 | pr_warning("Event GUID not found\n"); | ||
1592 | return -ENODEV; | ||
1593 | } | ||
1594 | |||
1595 | if (wdrv->probe) { | ||
1596 | ret = wdrv->probe(pdev); | ||
1597 | if (ret) | ||
1598 | return ret; | ||
1599 | } | ||
1600 | |||
1601 | return asus_wmi_add(pdev); | ||
1602 | } | ||
1603 | |||
1604 | static bool used; | ||
1605 | |||
1606 | int asus_wmi_register_driver(struct asus_wmi_driver *driver) | ||
1607 | { | ||
1608 | struct platform_driver *platform_driver; | ||
1609 | struct platform_device *platform_device; | ||
1610 | |||
1611 | if (used) | ||
1612 | return -EBUSY; | ||
1613 | |||
1614 | platform_driver = &driver->platform_driver; | ||
1615 | platform_driver->remove = asus_wmi_remove; | ||
1616 | platform_driver->driver.owner = driver->owner; | ||
1617 | platform_driver->driver.name = driver->name; | ||
1618 | platform_driver->driver.pm = &asus_pm_ops; | ||
1619 | |||
1620 | platform_device = platform_create_bundle(platform_driver, | ||
1621 | asus_wmi_probe, | ||
1622 | NULL, 0, NULL, 0); | ||
1623 | if (IS_ERR(platform_device)) | ||
1624 | return PTR_ERR(platform_device); | ||
1625 | |||
1626 | used = true; | ||
1627 | return 0; | ||
1628 | } | ||
1629 | EXPORT_SYMBOL_GPL(asus_wmi_register_driver); | ||
1630 | |||
1631 | void asus_wmi_unregister_driver(struct asus_wmi_driver *driver) | ||
1632 | { | ||
1633 | platform_device_unregister(driver->platform_device); | ||
1634 | platform_driver_unregister(&driver->platform_driver); | ||
1635 | used = false; | ||
1636 | } | ||
1637 | EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver); | ||
1638 | |||
1639 | static int __init asus_wmi_init(void) | ||
1640 | { | ||
1641 | if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) { | ||
1642 | pr_info("Asus Management GUID not found"); | ||
1643 | return -ENODEV; | ||
1644 | } | ||
1645 | |||
1646 | pr_info("ASUS WMI generic driver loaded"); | ||
1647 | return 0; | ||
1648 | } | ||
1649 | |||
1650 | static void __exit asus_wmi_exit(void) | ||
1651 | { | ||
1652 | pr_info("ASUS WMI generic driver unloaded"); | ||
1653 | } | ||
1654 | |||
1655 | module_init(asus_wmi_init); | ||
1656 | module_exit(asus_wmi_exit); | ||
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h new file mode 100644 index 000000000000..c044522c8766 --- /dev/null +++ b/drivers/platform/x86/asus-wmi.h | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * Asus PC WMI hotkey driver | ||
3 | * | ||
4 | * Copyright(C) 2010 Intel Corporation. | ||
5 | * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> | ||
6 | * | ||
7 | * Portions based on wistron_btns.c: | ||
8 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> | ||
9 | * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> | ||
10 | * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #ifndef _ASUS_WMI_H_ | ||
28 | #define _ASUS_WMI_H_ | ||
29 | |||
30 | #include <linux/platform_device.h> | ||
31 | |||
32 | struct module; | ||
33 | struct key_entry; | ||
34 | struct asus_wmi; | ||
35 | |||
36 | struct asus_wmi_driver { | ||
37 | bool hotplug_wireless; | ||
38 | |||
39 | const char *name; | ||
40 | struct module *owner; | ||
41 | |||
42 | const char *event_guid; | ||
43 | |||
44 | const struct key_entry *keymap; | ||
45 | const char *input_name; | ||
46 | const char *input_phys; | ||
47 | |||
48 | int (*probe) (struct platform_device *device); | ||
49 | void (*quirks) (struct asus_wmi_driver *driver); | ||
50 | |||
51 | struct platform_driver platform_driver; | ||
52 | struct platform_device *platform_device; | ||
53 | }; | ||
54 | |||
55 | int asus_wmi_register_driver(struct asus_wmi_driver *driver); | ||
56 | void asus_wmi_unregister_driver(struct asus_wmi_driver *driver); | ||
57 | |||
58 | #endif /* !_ASUS_WMI_H_ */ | ||
diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index eb95878fa583..c16a27641ced 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c | |||
@@ -201,7 +201,7 @@ static bool extra_features; | |||
201 | * into 0x4F and read a few bytes from the output, like so: | 201 | * into 0x4F and read a few bytes from the output, like so: |
202 | * u8 writeData = 0x33; | 202 | * u8 writeData = 0x33; |
203 | * ec_transaction(0x4F, &writeData, 1, buffer, 32, 0); | 203 | * ec_transaction(0x4F, &writeData, 1, buffer, 32, 0); |
204 | * That address is labled "fan1 table information" in the service manual. | 204 | * That address is labelled "fan1 table information" in the service manual. |
205 | * It should be clear which value in 'buffer' changes). This seems to be | 205 | * It should be clear which value in 'buffer' changes). This seems to be |
206 | * related to fan speed. It isn't a proper 'realtime' fan speed value | 206 | * related to fan speed. It isn't a proper 'realtime' fan speed value |
207 | * though, because physically stopping or speeding up the fan doesn't | 207 | * though, because physically stopping or speeding up the fan doesn't |
@@ -275,7 +275,7 @@ static int set_backlight_level(int level) | |||
275 | 275 | ||
276 | ec_write(BACKLIGHT_LEVEL_ADDR, level); | 276 | ec_write(BACKLIGHT_LEVEL_ADDR, level); |
277 | 277 | ||
278 | return 1; | 278 | return 0; |
279 | } | 279 | } |
280 | 280 | ||
281 | static int get_backlight_level(void) | 281 | static int get_backlight_level(void) |
@@ -763,7 +763,7 @@ static int dmi_check_cb(const struct dmi_system_id *id) | |||
763 | printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s'\n", | 763 | printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s'\n", |
764 | id->ident); | 764 | id->ident); |
765 | extra_features = false; | 765 | extra_features = false; |
766 | return 0; | 766 | return 1; |
767 | } | 767 | } |
768 | 768 | ||
769 | static int dmi_check_cb_extra(const struct dmi_system_id *id) | 769 | static int dmi_check_cb_extra(const struct dmi_system_id *id) |
@@ -772,7 +772,7 @@ static int dmi_check_cb_extra(const struct dmi_system_id *id) | |||
772 | "enabling extra features\n", | 772 | "enabling extra features\n", |
773 | id->ident); | 773 | id->ident); |
774 | extra_features = true; | 774 | extra_features = true; |
775 | return 0; | 775 | return 1; |
776 | } | 776 | } |
777 | 777 | ||
778 | static struct dmi_system_id __initdata compal_dmi_table[] = { | 778 | static struct dmi_system_id __initdata compal_dmi_table[] = { |
diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c new file mode 100644 index 000000000000..0ed84573ae1f --- /dev/null +++ b/drivers/platform/x86/dell-wmi-aio.c | |||
@@ -0,0 +1,171 @@ | |||
1 | /* | ||
2 | * WMI hotkeys support for Dell All-In-One series | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | */ | ||
18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
19 | |||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/types.h> | ||
24 | #include <linux/input.h> | ||
25 | #include <linux/input/sparse-keymap.h> | ||
26 | #include <acpi/acpi_drivers.h> | ||
27 | #include <linux/acpi.h> | ||
28 | #include <linux/string.h> | ||
29 | |||
30 | MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); | ||
31 | MODULE_LICENSE("GPL"); | ||
32 | |||
33 | #define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" | ||
34 | #define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" | ||
35 | |||
36 | static const char *dell_wmi_aio_guids[] = { | ||
37 | EVENT_GUID1, | ||
38 | EVENT_GUID2, | ||
39 | NULL | ||
40 | }; | ||
41 | |||
42 | MODULE_ALIAS("wmi:"EVENT_GUID1); | ||
43 | MODULE_ALIAS("wmi:"EVENT_GUID2); | ||
44 | |||
45 | static const struct key_entry dell_wmi_aio_keymap[] = { | ||
46 | { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, | ||
47 | { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, | ||
48 | { KE_END, 0 } | ||
49 | }; | ||
50 | |||
51 | static struct input_dev *dell_wmi_aio_input_dev; | ||
52 | |||
53 | static void dell_wmi_aio_notify(u32 value, void *context) | ||
54 | { | ||
55 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
56 | union acpi_object *obj; | ||
57 | acpi_status status; | ||
58 | |||
59 | status = wmi_get_event_data(value, &response); | ||
60 | if (status != AE_OK) { | ||
61 | pr_info("bad event status 0x%x\n", status); | ||
62 | return; | ||
63 | } | ||
64 | |||
65 | obj = (union acpi_object *)response.pointer; | ||
66 | if (obj) { | ||
67 | unsigned int scancode; | ||
68 | |||
69 | switch (obj->type) { | ||
70 | case ACPI_TYPE_INTEGER: | ||
71 | /* Most All-In-One correctly return integer scancode */ | ||
72 | scancode = obj->integer.value; | ||
73 | sparse_keymap_report_event(dell_wmi_aio_input_dev, | ||
74 | scancode, 1, true); | ||
75 | break; | ||
76 | case ACPI_TYPE_BUFFER: | ||
77 | /* Broken machines return the scancode in a buffer */ | ||
78 | if (obj->buffer.pointer && obj->buffer.length > 0) { | ||
79 | scancode = obj->buffer.pointer[0]; | ||
80 | sparse_keymap_report_event( | ||
81 | dell_wmi_aio_input_dev, | ||
82 | scancode, 1, true); | ||
83 | } | ||
84 | break; | ||
85 | } | ||
86 | } | ||
87 | kfree(obj); | ||
88 | } | ||
89 | |||
90 | static int __init dell_wmi_aio_input_setup(void) | ||
91 | { | ||
92 | int err; | ||
93 | |||
94 | dell_wmi_aio_input_dev = input_allocate_device(); | ||
95 | |||
96 | if (!dell_wmi_aio_input_dev) | ||
97 | return -ENOMEM; | ||
98 | |||
99 | dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; | ||
100 | dell_wmi_aio_input_dev->phys = "wmi/input0"; | ||
101 | dell_wmi_aio_input_dev->id.bustype = BUS_HOST; | ||
102 | |||
103 | err = sparse_keymap_setup(dell_wmi_aio_input_dev, | ||
104 | dell_wmi_aio_keymap, NULL); | ||
105 | if (err) { | ||
106 | pr_err("Unable to setup input device keymap\n"); | ||
107 | goto err_free_dev; | ||
108 | } | ||
109 | err = input_register_device(dell_wmi_aio_input_dev); | ||
110 | if (err) { | ||
111 | pr_info("Unable to register input device\n"); | ||
112 | goto err_free_keymap; | ||
113 | } | ||
114 | return 0; | ||
115 | |||
116 | err_free_keymap: | ||
117 | sparse_keymap_free(dell_wmi_aio_input_dev); | ||
118 | err_free_dev: | ||
119 | input_free_device(dell_wmi_aio_input_dev); | ||
120 | return err; | ||
121 | } | ||
122 | |||
123 | static const char *dell_wmi_aio_find(void) | ||
124 | { | ||
125 | int i; | ||
126 | |||
127 | for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) | ||
128 | if (wmi_has_guid(dell_wmi_aio_guids[i])) | ||
129 | return dell_wmi_aio_guids[i]; | ||
130 | |||
131 | return NULL; | ||
132 | } | ||
133 | |||
134 | static int __init dell_wmi_aio_init(void) | ||
135 | { | ||
136 | int err; | ||
137 | const char *guid; | ||
138 | |||
139 | guid = dell_wmi_aio_find(); | ||
140 | if (!guid) { | ||
141 | pr_warning("No known WMI GUID found\n"); | ||
142 | return -ENXIO; | ||
143 | } | ||
144 | |||
145 | err = dell_wmi_aio_input_setup(); | ||
146 | if (err) | ||
147 | return err; | ||
148 | |||
149 | err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); | ||
150 | if (err) { | ||
151 | pr_err("Unable to register notify handler - %d\n", err); | ||
152 | sparse_keymap_free(dell_wmi_aio_input_dev); | ||
153 | input_unregister_device(dell_wmi_aio_input_dev); | ||
154 | return err; | ||
155 | } | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static void __exit dell_wmi_aio_exit(void) | ||
161 | { | ||
162 | const char *guid; | ||
163 | |||
164 | guid = dell_wmi_aio_find(); | ||
165 | wmi_remove_notify_handler(guid); | ||
166 | sparse_keymap_free(dell_wmi_aio_input_dev); | ||
167 | input_unregister_device(dell_wmi_aio_input_dev); | ||
168 | } | ||
169 | |||
170 | module_init(dell_wmi_aio_init); | ||
171 | module_exit(dell_wmi_aio_exit); | ||
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 6605beac0d0e..5f2dd386152b 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c | |||
@@ -1322,7 +1322,7 @@ static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name) | |||
1322 | { | 1322 | { |
1323 | int dummy; | 1323 | int dummy; |
1324 | 1324 | ||
1325 | /* Some BIOSes do not report cm although it is avaliable. | 1325 | /* Some BIOSes do not report cm although it is available. |
1326 | Check if cm_getv[cm] works and, if yes, assume cm should be set. */ | 1326 | Check if cm_getv[cm] works and, if yes, assume cm should be set. */ |
1327 | if (!(eeepc->cm_supported & (1 << cm)) | 1327 | if (!(eeepc->cm_supported & (1 << cm)) |
1328 | && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) { | 1328 | && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) { |
diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c index 4d38f98aa976..0ddc434fb93b 100644 --- a/drivers/platform/x86/eeepc-wmi.c +++ b/drivers/platform/x86/eeepc-wmi.c | |||
@@ -2,7 +2,7 @@ | |||
2 | * Eee PC WMI hotkey driver | 2 | * Eee PC WMI hotkey driver |
3 | * | 3 | * |
4 | * Copyright(C) 2010 Intel Corporation. | 4 | * Copyright(C) 2010 Intel Corporation. |
5 | * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com> | 5 | * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com> |
6 | * | 6 | * |
7 | * Portions based on wistron_btns.c: | 7 | * Portions based on wistron_btns.c: |
8 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> | 8 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> |
@@ -29,841 +29,57 @@ | |||
29 | #include <linux/kernel.h> | 29 | #include <linux/kernel.h> |
30 | #include <linux/module.h> | 30 | #include <linux/module.h> |
31 | #include <linux/init.h> | 31 | #include <linux/init.h> |
32 | #include <linux/types.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/input.h> | 32 | #include <linux/input.h> |
35 | #include <linux/input/sparse-keymap.h> | 33 | #include <linux/input/sparse-keymap.h> |
36 | #include <linux/fb.h> | 34 | #include <linux/dmi.h> |
37 | #include <linux/backlight.h> | ||
38 | #include <linux/leds.h> | ||
39 | #include <linux/rfkill.h> | ||
40 | #include <linux/debugfs.h> | ||
41 | #include <linux/seq_file.h> | ||
42 | #include <linux/platform_device.h> | ||
43 | #include <acpi/acpi_bus.h> | 35 | #include <acpi/acpi_bus.h> |
44 | #include <acpi/acpi_drivers.h> | 36 | |
37 | #include "asus-wmi.h" | ||
45 | 38 | ||
46 | #define EEEPC_WMI_FILE "eeepc-wmi" | 39 | #define EEEPC_WMI_FILE "eeepc-wmi" |
47 | 40 | ||
48 | MODULE_AUTHOR("Yong Wang <yong.y.wang@intel.com>"); | 41 | MODULE_AUTHOR("Corentin Chary <corentincj@iksaif.net>"); |
49 | MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver"); | 42 | MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver"); |
50 | MODULE_LICENSE("GPL"); | 43 | MODULE_LICENSE("GPL"); |
51 | 44 | ||
52 | #define EEEPC_ACPI_HID "ASUS010" /* old _HID used in eeepc-laptop */ | 45 | #define EEEPC_ACPI_HID "ASUS010" /* old _HID used in eeepc-laptop */ |
53 | 46 | ||
54 | #define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" | 47 | #define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" |
55 | #define EEEPC_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" | ||
56 | 48 | ||
57 | MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID); | 49 | MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID); |
58 | MODULE_ALIAS("wmi:"EEEPC_WMI_MGMT_GUID); | ||
59 | |||
60 | #define NOTIFY_BRNUP_MIN 0x11 | ||
61 | #define NOTIFY_BRNUP_MAX 0x1f | ||
62 | #define NOTIFY_BRNDOWN_MIN 0x20 | ||
63 | #define NOTIFY_BRNDOWN_MAX 0x2e | ||
64 | 50 | ||
65 | #define EEEPC_WMI_METHODID_DEVS 0x53564544 | 51 | static bool hotplug_wireless; |
66 | #define EEEPC_WMI_METHODID_DSTS 0x53544344 | ||
67 | #define EEEPC_WMI_METHODID_CFVS 0x53564643 | ||
68 | 52 | ||
69 | #define EEEPC_WMI_DEVID_BACKLIGHT 0x00050012 | 53 | module_param(hotplug_wireless, bool, 0444); |
70 | #define EEEPC_WMI_DEVID_TPDLED 0x00100011 | 54 | MODULE_PARM_DESC(hotplug_wireless, |
71 | #define EEEPC_WMI_DEVID_WLAN 0x00010011 | 55 | "Enable hotplug for wireless device. " |
72 | #define EEEPC_WMI_DEVID_BLUETOOTH 0x00010013 | 56 | "If your laptop needs that, please report to " |
73 | #define EEEPC_WMI_DEVID_WWAN3G 0x00010019 | 57 | "acpi4asus-user@lists.sourceforge.net."); |
74 | 58 | ||
75 | static const struct key_entry eeepc_wmi_keymap[] = { | 59 | static const struct key_entry eeepc_wmi_keymap[] = { |
76 | /* Sleep already handled via generic ACPI code */ | 60 | /* Sleep already handled via generic ACPI code */ |
77 | { KE_KEY, 0x5d, { KEY_WLAN } }, | ||
78 | { KE_KEY, 0x32, { KEY_MUTE } }, | ||
79 | { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, | ||
80 | { KE_KEY, 0x30, { KEY_VOLUMEUP } }, | 61 | { KE_KEY, 0x30, { KEY_VOLUMEUP } }, |
81 | { KE_IGNORE, NOTIFY_BRNDOWN_MIN, { KEY_BRIGHTNESSDOWN } }, | 62 | { KE_KEY, 0x31, { KEY_VOLUMEDOWN } }, |
82 | { KE_IGNORE, NOTIFY_BRNUP_MIN, { KEY_BRIGHTNESSUP } }, | 63 | { KE_KEY, 0x32, { KEY_MUTE } }, |
64 | { KE_KEY, 0x5c, { KEY_F15 } }, /* Power Gear key */ | ||
65 | { KE_KEY, 0x5d, { KEY_WLAN } }, | ||
66 | { KE_KEY, 0x6b, { KEY_TOUCHPAD_TOGGLE } }, /* Toggle Touchpad */ | ||
67 | { KE_KEY, 0x82, { KEY_CAMERA } }, | ||
68 | { KE_KEY, 0x83, { KEY_CAMERA_ZOOMIN } }, | ||
69 | { KE_KEY, 0x88, { KEY_WLAN } }, | ||
83 | { KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } }, | 70 | { KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } }, |
84 | { KE_KEY, 0x6b, { KEY_F13 } }, /* Disable Touchpad */ | 71 | { KE_KEY, 0xe0, { KEY_PROG1 } }, /* Task Manager */ |
85 | { KE_KEY, 0xe1, { KEY_F14 } }, | 72 | { KE_KEY, 0xe1, { KEY_F14 } }, /* Change Resolution */ |
86 | { KE_KEY, 0xe9, { KEY_DISPLAY_OFF } }, | 73 | { KE_KEY, 0xe9, { KEY_BRIGHTNESS_ZERO } }, |
87 | { KE_KEY, 0xe0, { KEY_PROG1 } }, | 74 | { KE_KEY, 0xeb, { KEY_CAMERA_ZOOMOUT } }, |
88 | { KE_KEY, 0x5c, { KEY_F15 } }, | 75 | { KE_KEY, 0xec, { KEY_CAMERA_UP } }, |
76 | { KE_KEY, 0xed, { KEY_CAMERA_DOWN } }, | ||
77 | { KE_KEY, 0xee, { KEY_CAMERA_LEFT } }, | ||
78 | { KE_KEY, 0xef, { KEY_CAMERA_RIGHT } }, | ||
89 | { KE_END, 0}, | 79 | { KE_END, 0}, |
90 | }; | 80 | }; |
91 | 81 | ||
92 | struct bios_args { | 82 | static acpi_status eeepc_wmi_parse_device(acpi_handle handle, u32 level, |
93 | u32 dev_id; | ||
94 | u32 ctrl_param; | ||
95 | }; | ||
96 | |||
97 | /* | ||
98 | * eeepc-wmi/ - debugfs root directory | ||
99 | * dev_id - current dev_id | ||
100 | * ctrl_param - current ctrl_param | ||
101 | * devs - call DEVS(dev_id, ctrl_param) and print result | ||
102 | * dsts - call DSTS(dev_id) and print result | ||
103 | */ | ||
104 | struct eeepc_wmi_debug { | ||
105 | struct dentry *root; | ||
106 | u32 dev_id; | ||
107 | u32 ctrl_param; | ||
108 | }; | ||
109 | |||
110 | struct eeepc_wmi { | ||
111 | struct input_dev *inputdev; | ||
112 | struct backlight_device *backlight_device; | ||
113 | struct platform_device *platform_device; | ||
114 | |||
115 | struct led_classdev tpd_led; | ||
116 | int tpd_led_wk; | ||
117 | struct workqueue_struct *led_workqueue; | ||
118 | struct work_struct tpd_led_work; | ||
119 | |||
120 | struct rfkill *wlan_rfkill; | ||
121 | struct rfkill *bluetooth_rfkill; | ||
122 | struct rfkill *wwan3g_rfkill; | ||
123 | |||
124 | struct eeepc_wmi_debug debug; | ||
125 | }; | ||
126 | |||
127 | /* Only used in eeepc_wmi_init() and eeepc_wmi_exit() */ | ||
128 | static struct platform_device *platform_device; | ||
129 | |||
130 | static int eeepc_wmi_input_init(struct eeepc_wmi *eeepc) | ||
131 | { | ||
132 | int err; | ||
133 | |||
134 | eeepc->inputdev = input_allocate_device(); | ||
135 | if (!eeepc->inputdev) | ||
136 | return -ENOMEM; | ||
137 | |||
138 | eeepc->inputdev->name = "Eee PC WMI hotkeys"; | ||
139 | eeepc->inputdev->phys = EEEPC_WMI_FILE "/input0"; | ||
140 | eeepc->inputdev->id.bustype = BUS_HOST; | ||
141 | eeepc->inputdev->dev.parent = &eeepc->platform_device->dev; | ||
142 | |||
143 | err = sparse_keymap_setup(eeepc->inputdev, eeepc_wmi_keymap, NULL); | ||
144 | if (err) | ||
145 | goto err_free_dev; | ||
146 | |||
147 | err = input_register_device(eeepc->inputdev); | ||
148 | if (err) | ||
149 | goto err_free_keymap; | ||
150 | |||
151 | return 0; | ||
152 | |||
153 | err_free_keymap: | ||
154 | sparse_keymap_free(eeepc->inputdev); | ||
155 | err_free_dev: | ||
156 | input_free_device(eeepc->inputdev); | ||
157 | return err; | ||
158 | } | ||
159 | |||
160 | static void eeepc_wmi_input_exit(struct eeepc_wmi *eeepc) | ||
161 | { | ||
162 | if (eeepc->inputdev) { | ||
163 | sparse_keymap_free(eeepc->inputdev); | ||
164 | input_unregister_device(eeepc->inputdev); | ||
165 | } | ||
166 | |||
167 | eeepc->inputdev = NULL; | ||
168 | } | ||
169 | |||
170 | static acpi_status eeepc_wmi_get_devstate(u32 dev_id, u32 *retval) | ||
171 | { | ||
172 | struct acpi_buffer input = { (acpi_size)sizeof(u32), &dev_id }; | ||
173 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
174 | union acpi_object *obj; | ||
175 | acpi_status status; | ||
176 | u32 tmp; | ||
177 | |||
178 | status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, | ||
179 | 1, EEEPC_WMI_METHODID_DSTS, &input, &output); | ||
180 | |||
181 | if (ACPI_FAILURE(status)) | ||
182 | return status; | ||
183 | |||
184 | obj = (union acpi_object *)output.pointer; | ||
185 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
186 | tmp = (u32)obj->integer.value; | ||
187 | else | ||
188 | tmp = 0; | ||
189 | |||
190 | if (retval) | ||
191 | *retval = tmp; | ||
192 | |||
193 | kfree(obj); | ||
194 | |||
195 | return status; | ||
196 | |||
197 | } | ||
198 | |||
199 | static acpi_status eeepc_wmi_set_devstate(u32 dev_id, u32 ctrl_param, | ||
200 | u32 *retval) | ||
201 | { | ||
202 | struct bios_args args = { | ||
203 | .dev_id = dev_id, | ||
204 | .ctrl_param = ctrl_param, | ||
205 | }; | ||
206 | struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; | ||
207 | acpi_status status; | ||
208 | |||
209 | if (!retval) { | ||
210 | status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, 1, | ||
211 | EEEPC_WMI_METHODID_DEVS, | ||
212 | &input, NULL); | ||
213 | } else { | ||
214 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
215 | union acpi_object *obj; | ||
216 | u32 tmp; | ||
217 | |||
218 | status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, 1, | ||
219 | EEEPC_WMI_METHODID_DEVS, | ||
220 | &input, &output); | ||
221 | |||
222 | if (ACPI_FAILURE(status)) | ||
223 | return status; | ||
224 | |||
225 | obj = (union acpi_object *)output.pointer; | ||
226 | if (obj && obj->type == ACPI_TYPE_INTEGER) | ||
227 | tmp = (u32)obj->integer.value; | ||
228 | else | ||
229 | tmp = 0; | ||
230 | |||
231 | *retval = tmp; | ||
232 | |||
233 | kfree(obj); | ||
234 | } | ||
235 | |||
236 | return status; | ||
237 | } | ||
238 | |||
239 | /* | ||
240 | * LEDs | ||
241 | */ | ||
242 | /* | ||
243 | * These functions actually update the LED's, and are called from a | ||
244 | * workqueue. By doing this as separate work rather than when the LED | ||
245 | * subsystem asks, we avoid messing with the Eeepc ACPI stuff during a | ||
246 | * potentially bad time, such as a timer interrupt. | ||
247 | */ | ||
248 | static void tpd_led_update(struct work_struct *work) | ||
249 | { | ||
250 | int ctrl_param; | ||
251 | struct eeepc_wmi *eeepc; | ||
252 | |||
253 | eeepc = container_of(work, struct eeepc_wmi, tpd_led_work); | ||
254 | |||
255 | ctrl_param = eeepc->tpd_led_wk; | ||
256 | eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_TPDLED, ctrl_param, NULL); | ||
257 | } | ||
258 | |||
259 | static void tpd_led_set(struct led_classdev *led_cdev, | ||
260 | enum led_brightness value) | ||
261 | { | ||
262 | struct eeepc_wmi *eeepc; | ||
263 | |||
264 | eeepc = container_of(led_cdev, struct eeepc_wmi, tpd_led); | ||
265 | |||
266 | eeepc->tpd_led_wk = !!value; | ||
267 | queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work); | ||
268 | } | ||
269 | |||
270 | static int read_tpd_state(struct eeepc_wmi *eeepc) | ||
271 | { | ||
272 | u32 retval; | ||
273 | acpi_status status; | ||
274 | |||
275 | status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_TPDLED, &retval); | ||
276 | |||
277 | if (ACPI_FAILURE(status)) | ||
278 | return -1; | ||
279 | else if (!retval || retval == 0x00060000) | ||
280 | /* | ||
281 | * if touchpad led is present, DSTS will set some bits, | ||
282 | * usually 0x00020000. | ||
283 | * 0x00060000 means that the device is not supported | ||
284 | */ | ||
285 | return -ENODEV; | ||
286 | else | ||
287 | /* Status is stored in the first bit */ | ||
288 | return retval & 0x1; | ||
289 | } | ||
290 | |||
291 | static enum led_brightness tpd_led_get(struct led_classdev *led_cdev) | ||
292 | { | ||
293 | struct eeepc_wmi *eeepc; | ||
294 | |||
295 | eeepc = container_of(led_cdev, struct eeepc_wmi, tpd_led); | ||
296 | |||
297 | return read_tpd_state(eeepc); | ||
298 | } | ||
299 | |||
300 | static int eeepc_wmi_led_init(struct eeepc_wmi *eeepc) | ||
301 | { | ||
302 | int rv; | ||
303 | |||
304 | if (read_tpd_state(eeepc) < 0) | ||
305 | return 0; | ||
306 | |||
307 | eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue"); | ||
308 | if (!eeepc->led_workqueue) | ||
309 | return -ENOMEM; | ||
310 | INIT_WORK(&eeepc->tpd_led_work, tpd_led_update); | ||
311 | |||
312 | eeepc->tpd_led.name = "eeepc::touchpad"; | ||
313 | eeepc->tpd_led.brightness_set = tpd_led_set; | ||
314 | eeepc->tpd_led.brightness_get = tpd_led_get; | ||
315 | eeepc->tpd_led.max_brightness = 1; | ||
316 | |||
317 | rv = led_classdev_register(&eeepc->platform_device->dev, | ||
318 | &eeepc->tpd_led); | ||
319 | if (rv) { | ||
320 | destroy_workqueue(eeepc->led_workqueue); | ||
321 | return rv; | ||
322 | } | ||
323 | |||
324 | return 0; | ||
325 | } | ||
326 | |||
327 | static void eeepc_wmi_led_exit(struct eeepc_wmi *eeepc) | ||
328 | { | ||
329 | if (eeepc->tpd_led.dev) | ||
330 | led_classdev_unregister(&eeepc->tpd_led); | ||
331 | if (eeepc->led_workqueue) | ||
332 | destroy_workqueue(eeepc->led_workqueue); | ||
333 | } | ||
334 | |||
335 | /* | ||
336 | * Rfkill devices | ||
337 | */ | ||
338 | static int eeepc_rfkill_set(void *data, bool blocked) | ||
339 | { | ||
340 | int dev_id = (unsigned long)data; | ||
341 | u32 ctrl_param = !blocked; | ||
342 | |||
343 | return eeepc_wmi_set_devstate(dev_id, ctrl_param, NULL); | ||
344 | } | ||
345 | |||
346 | static void eeepc_rfkill_query(struct rfkill *rfkill, void *data) | ||
347 | { | ||
348 | int dev_id = (unsigned long)data; | ||
349 | u32 retval; | ||
350 | acpi_status status; | ||
351 | |||
352 | status = eeepc_wmi_get_devstate(dev_id, &retval); | ||
353 | |||
354 | if (ACPI_FAILURE(status)) | ||
355 | return ; | ||
356 | |||
357 | rfkill_set_sw_state(rfkill, !(retval & 0x1)); | ||
358 | } | ||
359 | |||
360 | static const struct rfkill_ops eeepc_rfkill_ops = { | ||
361 | .set_block = eeepc_rfkill_set, | ||
362 | .query = eeepc_rfkill_query, | ||
363 | }; | ||
364 | |||
365 | static int eeepc_new_rfkill(struct eeepc_wmi *eeepc, | ||
366 | struct rfkill **rfkill, | ||
367 | const char *name, | ||
368 | enum rfkill_type type, int dev_id) | ||
369 | { | ||
370 | int result; | ||
371 | u32 retval; | ||
372 | acpi_status status; | ||
373 | |||
374 | status = eeepc_wmi_get_devstate(dev_id, &retval); | ||
375 | |||
376 | if (ACPI_FAILURE(status)) | ||
377 | return -1; | ||
378 | |||
379 | /* If the device is present, DSTS will always set some bits | ||
380 | * 0x00070000 - 1110000000000000000 - device supported | ||
381 | * 0x00060000 - 1100000000000000000 - not supported | ||
382 | * 0x00020000 - 0100000000000000000 - device supported | ||
383 | * 0x00010000 - 0010000000000000000 - not supported / special mode ? | ||
384 | */ | ||
385 | if (!retval || retval == 0x00060000) | ||
386 | return -ENODEV; | ||
387 | |||
388 | *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type, | ||
389 | &eeepc_rfkill_ops, (void *)(long)dev_id); | ||
390 | |||
391 | if (!*rfkill) | ||
392 | return -EINVAL; | ||
393 | |||
394 | rfkill_init_sw_state(*rfkill, !(retval & 0x1)); | ||
395 | result = rfkill_register(*rfkill); | ||
396 | if (result) { | ||
397 | rfkill_destroy(*rfkill); | ||
398 | *rfkill = NULL; | ||
399 | return result; | ||
400 | } | ||
401 | return 0; | ||
402 | } | ||
403 | |||
404 | static void eeepc_wmi_rfkill_exit(struct eeepc_wmi *eeepc) | ||
405 | { | ||
406 | if (eeepc->wlan_rfkill) { | ||
407 | rfkill_unregister(eeepc->wlan_rfkill); | ||
408 | rfkill_destroy(eeepc->wlan_rfkill); | ||
409 | eeepc->wlan_rfkill = NULL; | ||
410 | } | ||
411 | if (eeepc->bluetooth_rfkill) { | ||
412 | rfkill_unregister(eeepc->bluetooth_rfkill); | ||
413 | rfkill_destroy(eeepc->bluetooth_rfkill); | ||
414 | eeepc->bluetooth_rfkill = NULL; | ||
415 | } | ||
416 | if (eeepc->wwan3g_rfkill) { | ||
417 | rfkill_unregister(eeepc->wwan3g_rfkill); | ||
418 | rfkill_destroy(eeepc->wwan3g_rfkill); | ||
419 | eeepc->wwan3g_rfkill = NULL; | ||
420 | } | ||
421 | } | ||
422 | |||
423 | static int eeepc_wmi_rfkill_init(struct eeepc_wmi *eeepc) | ||
424 | { | ||
425 | int result = 0; | ||
426 | |||
427 | result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill, | ||
428 | "eeepc-wlan", RFKILL_TYPE_WLAN, | ||
429 | EEEPC_WMI_DEVID_WLAN); | ||
430 | |||
431 | if (result && result != -ENODEV) | ||
432 | goto exit; | ||
433 | |||
434 | result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill, | ||
435 | "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH, | ||
436 | EEEPC_WMI_DEVID_BLUETOOTH); | ||
437 | |||
438 | if (result && result != -ENODEV) | ||
439 | goto exit; | ||
440 | |||
441 | result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill, | ||
442 | "eeepc-wwan3g", RFKILL_TYPE_WWAN, | ||
443 | EEEPC_WMI_DEVID_WWAN3G); | ||
444 | |||
445 | if (result && result != -ENODEV) | ||
446 | goto exit; | ||
447 | |||
448 | exit: | ||
449 | if (result && result != -ENODEV) | ||
450 | eeepc_wmi_rfkill_exit(eeepc); | ||
451 | |||
452 | if (result == -ENODEV) | ||
453 | result = 0; | ||
454 | |||
455 | return result; | ||
456 | } | ||
457 | |||
458 | /* | ||
459 | * Backlight | ||
460 | */ | ||
461 | static int read_brightness(struct backlight_device *bd) | ||
462 | { | ||
463 | u32 retval; | ||
464 | acpi_status status; | ||
465 | |||
466 | status = eeepc_wmi_get_devstate(EEEPC_WMI_DEVID_BACKLIGHT, &retval); | ||
467 | |||
468 | if (ACPI_FAILURE(status)) | ||
469 | return -1; | ||
470 | else | ||
471 | return retval & 0xFF; | ||
472 | } | ||
473 | |||
474 | static int update_bl_status(struct backlight_device *bd) | ||
475 | { | ||
476 | |||
477 | u32 ctrl_param; | ||
478 | acpi_status status; | ||
479 | |||
480 | ctrl_param = bd->props.brightness; | ||
481 | |||
482 | status = eeepc_wmi_set_devstate(EEEPC_WMI_DEVID_BACKLIGHT, | ||
483 | ctrl_param, NULL); | ||
484 | |||
485 | if (ACPI_FAILURE(status)) | ||
486 | return -1; | ||
487 | else | ||
488 | return 0; | ||
489 | } | ||
490 | |||
491 | static const struct backlight_ops eeepc_wmi_bl_ops = { | ||
492 | .get_brightness = read_brightness, | ||
493 | .update_status = update_bl_status, | ||
494 | }; | ||
495 | |||
496 | static int eeepc_wmi_backlight_notify(struct eeepc_wmi *eeepc, int code) | ||
497 | { | ||
498 | struct backlight_device *bd = eeepc->backlight_device; | ||
499 | int old = bd->props.brightness; | ||
500 | int new = old; | ||
501 | |||
502 | if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) | ||
503 | new = code - NOTIFY_BRNUP_MIN + 1; | ||
504 | else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) | ||
505 | new = code - NOTIFY_BRNDOWN_MIN; | ||
506 | |||
507 | bd->props.brightness = new; | ||
508 | backlight_update_status(bd); | ||
509 | backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); | ||
510 | |||
511 | return old; | ||
512 | } | ||
513 | |||
514 | static int eeepc_wmi_backlight_init(struct eeepc_wmi *eeepc) | ||
515 | { | ||
516 | struct backlight_device *bd; | ||
517 | struct backlight_properties props; | ||
518 | |||
519 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
520 | props.max_brightness = 15; | ||
521 | bd = backlight_device_register(EEEPC_WMI_FILE, | ||
522 | &eeepc->platform_device->dev, eeepc, | ||
523 | &eeepc_wmi_bl_ops, &props); | ||
524 | if (IS_ERR(bd)) { | ||
525 | pr_err("Could not register backlight device\n"); | ||
526 | return PTR_ERR(bd); | ||
527 | } | ||
528 | |||
529 | eeepc->backlight_device = bd; | ||
530 | |||
531 | bd->props.brightness = read_brightness(bd); | ||
532 | bd->props.power = FB_BLANK_UNBLANK; | ||
533 | backlight_update_status(bd); | ||
534 | |||
535 | return 0; | ||
536 | } | ||
537 | |||
538 | static void eeepc_wmi_backlight_exit(struct eeepc_wmi *eeepc) | ||
539 | { | ||
540 | if (eeepc->backlight_device) | ||
541 | backlight_device_unregister(eeepc->backlight_device); | ||
542 | |||
543 | eeepc->backlight_device = NULL; | ||
544 | } | ||
545 | |||
546 | static void eeepc_wmi_notify(u32 value, void *context) | ||
547 | { | ||
548 | struct eeepc_wmi *eeepc = context; | ||
549 | struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
550 | union acpi_object *obj; | ||
551 | acpi_status status; | ||
552 | int code; | ||
553 | int orig_code; | ||
554 | |||
555 | status = wmi_get_event_data(value, &response); | ||
556 | if (status != AE_OK) { | ||
557 | pr_err("bad event status 0x%x\n", status); | ||
558 | return; | ||
559 | } | ||
560 | |||
561 | obj = (union acpi_object *)response.pointer; | ||
562 | |||
563 | if (obj && obj->type == ACPI_TYPE_INTEGER) { | ||
564 | code = obj->integer.value; | ||
565 | orig_code = code; | ||
566 | |||
567 | if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) | ||
568 | code = NOTIFY_BRNUP_MIN; | ||
569 | else if (code >= NOTIFY_BRNDOWN_MIN && | ||
570 | code <= NOTIFY_BRNDOWN_MAX) | ||
571 | code = NOTIFY_BRNDOWN_MIN; | ||
572 | |||
573 | if (code == NOTIFY_BRNUP_MIN || code == NOTIFY_BRNDOWN_MIN) { | ||
574 | if (!acpi_video_backlight_support()) | ||
575 | eeepc_wmi_backlight_notify(eeepc, orig_code); | ||
576 | } | ||
577 | |||
578 | if (!sparse_keymap_report_event(eeepc->inputdev, | ||
579 | code, 1, true)) | ||
580 | pr_info("Unknown key %x pressed\n", code); | ||
581 | } | ||
582 | |||
583 | kfree(obj); | ||
584 | } | ||
585 | |||
586 | static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, | ||
587 | const char *buf, size_t count) | ||
588 | { | ||
589 | int value; | ||
590 | struct acpi_buffer input = { (acpi_size)sizeof(value), &value }; | ||
591 | acpi_status status; | ||
592 | |||
593 | if (!count || sscanf(buf, "%i", &value) != 1) | ||
594 | return -EINVAL; | ||
595 | if (value < 0 || value > 2) | ||
596 | return -EINVAL; | ||
597 | |||
598 | status = wmi_evaluate_method(EEEPC_WMI_MGMT_GUID, | ||
599 | 1, EEEPC_WMI_METHODID_CFVS, &input, NULL); | ||
600 | |||
601 | if (ACPI_FAILURE(status)) | ||
602 | return -EIO; | ||
603 | else | ||
604 | return count; | ||
605 | } | ||
606 | |||
607 | static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv); | ||
608 | |||
609 | static struct attribute *platform_attributes[] = { | ||
610 | &dev_attr_cpufv.attr, | ||
611 | NULL | ||
612 | }; | ||
613 | |||
614 | static struct attribute_group platform_attribute_group = { | ||
615 | .attrs = platform_attributes | ||
616 | }; | ||
617 | |||
618 | static void eeepc_wmi_sysfs_exit(struct platform_device *device) | ||
619 | { | ||
620 | sysfs_remove_group(&device->dev.kobj, &platform_attribute_group); | ||
621 | } | ||
622 | |||
623 | static int eeepc_wmi_sysfs_init(struct platform_device *device) | ||
624 | { | ||
625 | return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); | ||
626 | } | ||
627 | |||
628 | /* | ||
629 | * Platform device | ||
630 | */ | ||
631 | static int __init eeepc_wmi_platform_init(struct eeepc_wmi *eeepc) | ||
632 | { | ||
633 | int err; | ||
634 | |||
635 | eeepc->platform_device = platform_device_alloc(EEEPC_WMI_FILE, -1); | ||
636 | if (!eeepc->platform_device) | ||
637 | return -ENOMEM; | ||
638 | platform_set_drvdata(eeepc->platform_device, eeepc); | ||
639 | |||
640 | err = platform_device_add(eeepc->platform_device); | ||
641 | if (err) | ||
642 | goto fail_platform_device; | ||
643 | |||
644 | err = eeepc_wmi_sysfs_init(eeepc->platform_device); | ||
645 | if (err) | ||
646 | goto fail_sysfs; | ||
647 | return 0; | ||
648 | |||
649 | fail_sysfs: | ||
650 | platform_device_del(eeepc->platform_device); | ||
651 | fail_platform_device: | ||
652 | platform_device_put(eeepc->platform_device); | ||
653 | return err; | ||
654 | } | ||
655 | |||
656 | static void eeepc_wmi_platform_exit(struct eeepc_wmi *eeepc) | ||
657 | { | ||
658 | eeepc_wmi_sysfs_exit(eeepc->platform_device); | ||
659 | platform_device_unregister(eeepc->platform_device); | ||
660 | } | ||
661 | |||
662 | /* | ||
663 | * debugfs | ||
664 | */ | ||
665 | struct eeepc_wmi_debugfs_node { | ||
666 | struct eeepc_wmi *eeepc; | ||
667 | char *name; | ||
668 | int (*show)(struct seq_file *m, void *data); | ||
669 | }; | ||
670 | |||
671 | static int show_dsts(struct seq_file *m, void *data) | ||
672 | { | ||
673 | struct eeepc_wmi *eeepc = m->private; | ||
674 | acpi_status status; | ||
675 | u32 retval = -1; | ||
676 | |||
677 | status = eeepc_wmi_get_devstate(eeepc->debug.dev_id, &retval); | ||
678 | |||
679 | if (ACPI_FAILURE(status)) | ||
680 | return -EIO; | ||
681 | |||
682 | seq_printf(m, "DSTS(%x) = %x\n", eeepc->debug.dev_id, retval); | ||
683 | |||
684 | return 0; | ||
685 | } | ||
686 | |||
687 | static int show_devs(struct seq_file *m, void *data) | ||
688 | { | ||
689 | struct eeepc_wmi *eeepc = m->private; | ||
690 | acpi_status status; | ||
691 | u32 retval = -1; | ||
692 | |||
693 | status = eeepc_wmi_set_devstate(eeepc->debug.dev_id, | ||
694 | eeepc->debug.ctrl_param, &retval); | ||
695 | if (ACPI_FAILURE(status)) | ||
696 | return -EIO; | ||
697 | |||
698 | seq_printf(m, "DEVS(%x, %x) = %x\n", eeepc->debug.dev_id, | ||
699 | eeepc->debug.ctrl_param, retval); | ||
700 | |||
701 | return 0; | ||
702 | } | ||
703 | |||
704 | static struct eeepc_wmi_debugfs_node eeepc_wmi_debug_files[] = { | ||
705 | { NULL, "devs", show_devs }, | ||
706 | { NULL, "dsts", show_dsts }, | ||
707 | }; | ||
708 | |||
709 | static int eeepc_wmi_debugfs_open(struct inode *inode, struct file *file) | ||
710 | { | ||
711 | struct eeepc_wmi_debugfs_node *node = inode->i_private; | ||
712 | |||
713 | return single_open(file, node->show, node->eeepc); | ||
714 | } | ||
715 | |||
716 | static const struct file_operations eeepc_wmi_debugfs_io_ops = { | ||
717 | .owner = THIS_MODULE, | ||
718 | .open = eeepc_wmi_debugfs_open, | ||
719 | .read = seq_read, | ||
720 | .llseek = seq_lseek, | ||
721 | .release = single_release, | ||
722 | }; | ||
723 | |||
724 | static void eeepc_wmi_debugfs_exit(struct eeepc_wmi *eeepc) | ||
725 | { | ||
726 | debugfs_remove_recursive(eeepc->debug.root); | ||
727 | } | ||
728 | |||
729 | static int eeepc_wmi_debugfs_init(struct eeepc_wmi *eeepc) | ||
730 | { | ||
731 | struct dentry *dent; | ||
732 | int i; | ||
733 | |||
734 | eeepc->debug.root = debugfs_create_dir(EEEPC_WMI_FILE, NULL); | ||
735 | if (!eeepc->debug.root) { | ||
736 | pr_err("failed to create debugfs directory"); | ||
737 | goto error_debugfs; | ||
738 | } | ||
739 | |||
740 | dent = debugfs_create_x32("dev_id", S_IRUGO|S_IWUSR, | ||
741 | eeepc->debug.root, &eeepc->debug.dev_id); | ||
742 | if (!dent) | ||
743 | goto error_debugfs; | ||
744 | |||
745 | dent = debugfs_create_x32("ctrl_param", S_IRUGO|S_IWUSR, | ||
746 | eeepc->debug.root, &eeepc->debug.ctrl_param); | ||
747 | if (!dent) | ||
748 | goto error_debugfs; | ||
749 | |||
750 | for (i = 0; i < ARRAY_SIZE(eeepc_wmi_debug_files); i++) { | ||
751 | struct eeepc_wmi_debugfs_node *node = &eeepc_wmi_debug_files[i]; | ||
752 | |||
753 | node->eeepc = eeepc; | ||
754 | dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, | ||
755 | eeepc->debug.root, node, | ||
756 | &eeepc_wmi_debugfs_io_ops); | ||
757 | if (!dent) { | ||
758 | pr_err("failed to create debug file: %s\n", node->name); | ||
759 | goto error_debugfs; | ||
760 | } | ||
761 | } | ||
762 | |||
763 | return 0; | ||
764 | |||
765 | error_debugfs: | ||
766 | eeepc_wmi_debugfs_exit(eeepc); | ||
767 | return -ENOMEM; | ||
768 | } | ||
769 | |||
770 | /* | ||
771 | * WMI Driver | ||
772 | */ | ||
773 | static struct platform_device * __init eeepc_wmi_add(void) | ||
774 | { | ||
775 | struct eeepc_wmi *eeepc; | ||
776 | acpi_status status; | ||
777 | int err; | ||
778 | |||
779 | eeepc = kzalloc(sizeof(struct eeepc_wmi), GFP_KERNEL); | ||
780 | if (!eeepc) | ||
781 | return ERR_PTR(-ENOMEM); | ||
782 | |||
783 | /* | ||
784 | * Register the platform device first. It is used as a parent for the | ||
785 | * sub-devices below. | ||
786 | */ | ||
787 | err = eeepc_wmi_platform_init(eeepc); | ||
788 | if (err) | ||
789 | goto fail_platform; | ||
790 | |||
791 | err = eeepc_wmi_input_init(eeepc); | ||
792 | if (err) | ||
793 | goto fail_input; | ||
794 | |||
795 | err = eeepc_wmi_led_init(eeepc); | ||
796 | if (err) | ||
797 | goto fail_leds; | ||
798 | |||
799 | err = eeepc_wmi_rfkill_init(eeepc); | ||
800 | if (err) | ||
801 | goto fail_rfkill; | ||
802 | |||
803 | if (!acpi_video_backlight_support()) { | ||
804 | err = eeepc_wmi_backlight_init(eeepc); | ||
805 | if (err) | ||
806 | goto fail_backlight; | ||
807 | } else | ||
808 | pr_info("Backlight controlled by ACPI video driver\n"); | ||
809 | |||
810 | status = wmi_install_notify_handler(EEEPC_WMI_EVENT_GUID, | ||
811 | eeepc_wmi_notify, eeepc); | ||
812 | if (ACPI_FAILURE(status)) { | ||
813 | pr_err("Unable to register notify handler - %d\n", | ||
814 | status); | ||
815 | err = -ENODEV; | ||
816 | goto fail_wmi_handler; | ||
817 | } | ||
818 | |||
819 | err = eeepc_wmi_debugfs_init(eeepc); | ||
820 | if (err) | ||
821 | goto fail_debugfs; | ||
822 | |||
823 | return eeepc->platform_device; | ||
824 | |||
825 | fail_debugfs: | ||
826 | wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID); | ||
827 | fail_wmi_handler: | ||
828 | eeepc_wmi_backlight_exit(eeepc); | ||
829 | fail_backlight: | ||
830 | eeepc_wmi_rfkill_exit(eeepc); | ||
831 | fail_rfkill: | ||
832 | eeepc_wmi_led_exit(eeepc); | ||
833 | fail_leds: | ||
834 | eeepc_wmi_input_exit(eeepc); | ||
835 | fail_input: | ||
836 | eeepc_wmi_platform_exit(eeepc); | ||
837 | fail_platform: | ||
838 | kfree(eeepc); | ||
839 | return ERR_PTR(err); | ||
840 | } | ||
841 | |||
842 | static int eeepc_wmi_remove(struct platform_device *device) | ||
843 | { | ||
844 | struct eeepc_wmi *eeepc; | ||
845 | |||
846 | eeepc = platform_get_drvdata(device); | ||
847 | wmi_remove_notify_handler(EEEPC_WMI_EVENT_GUID); | ||
848 | eeepc_wmi_backlight_exit(eeepc); | ||
849 | eeepc_wmi_input_exit(eeepc); | ||
850 | eeepc_wmi_led_exit(eeepc); | ||
851 | eeepc_wmi_rfkill_exit(eeepc); | ||
852 | eeepc_wmi_debugfs_exit(eeepc); | ||
853 | eeepc_wmi_platform_exit(eeepc); | ||
854 | |||
855 | kfree(eeepc); | ||
856 | return 0; | ||
857 | } | ||
858 | |||
859 | static struct platform_driver platform_driver = { | ||
860 | .driver = { | ||
861 | .name = EEEPC_WMI_FILE, | ||
862 | .owner = THIS_MODULE, | ||
863 | }, | ||
864 | }; | ||
865 | |||
866 | static acpi_status __init eeepc_wmi_parse_device(acpi_handle handle, u32 level, | ||
867 | void *context, void **retval) | 83 | void *context, void **retval) |
868 | { | 84 | { |
869 | pr_warning("Found legacy ATKD device (%s)", EEEPC_ACPI_HID); | 85 | pr_warning("Found legacy ATKD device (%s)", EEEPC_ACPI_HID); |
@@ -871,7 +87,7 @@ static acpi_status __init eeepc_wmi_parse_device(acpi_handle handle, u32 level, | |||
871 | return AE_CTRL_TERMINATE; | 87 | return AE_CTRL_TERMINATE; |
872 | } | 88 | } |
873 | 89 | ||
874 | static int __init eeepc_wmi_check_atkd(void) | 90 | static int eeepc_wmi_check_atkd(void) |
875 | { | 91 | { |
876 | acpi_status status; | 92 | acpi_status status; |
877 | bool found = false; | 93 | bool found = false; |
@@ -884,16 +100,8 @@ static int __init eeepc_wmi_check_atkd(void) | |||
884 | return -1; | 100 | return -1; |
885 | } | 101 | } |
886 | 102 | ||
887 | static int __init eeepc_wmi_init(void) | 103 | static int eeepc_wmi_probe(struct platform_device *pdev) |
888 | { | 104 | { |
889 | int err; | ||
890 | |||
891 | if (!wmi_has_guid(EEEPC_WMI_EVENT_GUID) || | ||
892 | !wmi_has_guid(EEEPC_WMI_MGMT_GUID)) { | ||
893 | pr_warning("No known WMI GUID found\n"); | ||
894 | return -ENODEV; | ||
895 | } | ||
896 | |||
897 | if (eeepc_wmi_check_atkd()) { | 105 | if (eeepc_wmi_check_atkd()) { |
898 | pr_warning("WMI device present, but legacy ATKD device is also " | 106 | pr_warning("WMI device present, but legacy ATKD device is also " |
899 | "present and enabled."); | 107 | "present and enabled."); |
@@ -901,33 +109,59 @@ static int __init eeepc_wmi_init(void) | |||
901 | "acpi_osi=\"!Windows 2009\""); | 109 | "acpi_osi=\"!Windows 2009\""); |
902 | pr_warning("Can't load eeepc-wmi, use default acpi_osi " | 110 | pr_warning("Can't load eeepc-wmi, use default acpi_osi " |
903 | "(preferred) or eeepc-laptop"); | 111 | "(preferred) or eeepc-laptop"); |
904 | return -ENODEV; | 112 | return -EBUSY; |
905 | } | 113 | } |
114 | return 0; | ||
115 | } | ||
906 | 116 | ||
907 | platform_device = eeepc_wmi_add(); | 117 | static void eeepc_dmi_check(struct asus_wmi_driver *driver) |
908 | if (IS_ERR(platform_device)) { | 118 | { |
909 | err = PTR_ERR(platform_device); | 119 | const char *model; |
910 | goto fail_eeepc_wmi; | 120 | |
911 | } | 121 | model = dmi_get_system_info(DMI_PRODUCT_NAME); |
122 | if (!model) | ||
123 | return; | ||
912 | 124 | ||
913 | err = platform_driver_register(&platform_driver); | 125 | /* |
914 | if (err) { | 126 | * Whitelist for wlan hotplug |
915 | pr_warning("Unable to register platform driver\n"); | 127 | * |
916 | goto fail_platform_driver; | 128 | * Asus 1000H needs the current hotplug code to handle |
129 | * Fn+F2 correctly. We may add other Asus here later, but | ||
130 | * it seems that most of the laptops supported by asus-wmi | ||
131 | * don't need to be on this list | ||
132 | */ | ||
133 | if (strcmp(model, "1000H") == 0) { | ||
134 | driver->hotplug_wireless = true; | ||
135 | pr_info("wlan hotplug enabled\n"); | ||
917 | } | 136 | } |
137 | } | ||
138 | |||
139 | static void eeepc_wmi_quirks(struct asus_wmi_driver *driver) | ||
140 | { | ||
141 | driver->hotplug_wireless = hotplug_wireless; | ||
142 | eeepc_dmi_check(driver); | ||
143 | } | ||
144 | |||
145 | static struct asus_wmi_driver asus_wmi_driver = { | ||
146 | .name = EEEPC_WMI_FILE, | ||
147 | .owner = THIS_MODULE, | ||
148 | .event_guid = EEEPC_WMI_EVENT_GUID, | ||
149 | .keymap = eeepc_wmi_keymap, | ||
150 | .input_name = "Eee PC WMI hotkeys", | ||
151 | .input_phys = EEEPC_WMI_FILE "/input0", | ||
152 | .probe = eeepc_wmi_probe, | ||
153 | .quirks = eeepc_wmi_quirks, | ||
154 | }; | ||
918 | 155 | ||
919 | return 0; | ||
920 | 156 | ||
921 | fail_platform_driver: | 157 | static int __init eeepc_wmi_init(void) |
922 | eeepc_wmi_remove(platform_device); | 158 | { |
923 | fail_eeepc_wmi: | 159 | return asus_wmi_register_driver(&asus_wmi_driver); |
924 | return err; | ||
925 | } | 160 | } |
926 | 161 | ||
927 | static void __exit eeepc_wmi_exit(void) | 162 | static void __exit eeepc_wmi_exit(void) |
928 | { | 163 | { |
929 | eeepc_wmi_remove(platform_device); | 164 | asus_wmi_unregister_driver(&asus_wmi_driver); |
930 | platform_driver_unregister(&platform_driver); | ||
931 | } | 165 | } |
932 | 166 | ||
933 | module_init(eeepc_wmi_init); | 167 | module_init(eeepc_wmi_init); |
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 9e05af9c41cb..1bc4a7539ba9 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c | |||
@@ -2,6 +2,7 @@ | |||
2 | * HP WMI hotkeys | 2 | * HP WMI hotkeys |
3 | * | 3 | * |
4 | * Copyright (C) 2008 Red Hat <mjg@redhat.com> | 4 | * Copyright (C) 2008 Red Hat <mjg@redhat.com> |
5 | * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi> | ||
5 | * | 6 | * |
6 | * Portions based on wistron_btns.c: | 7 | * Portions based on wistron_btns.c: |
7 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> | 8 | * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> |
@@ -51,6 +52,7 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); | |||
51 | #define HPWMI_HARDWARE_QUERY 0x4 | 52 | #define HPWMI_HARDWARE_QUERY 0x4 |
52 | #define HPWMI_WIRELESS_QUERY 0x5 | 53 | #define HPWMI_WIRELESS_QUERY 0x5 |
53 | #define HPWMI_HOTKEY_QUERY 0xc | 54 | #define HPWMI_HOTKEY_QUERY 0xc |
55 | #define HPWMI_WIRELESS2_QUERY 0x1b | ||
54 | 56 | ||
55 | #define PREFIX "HP WMI: " | 57 | #define PREFIX "HP WMI: " |
56 | #define UNIMP "Unimplemented " | 58 | #define UNIMP "Unimplemented " |
@@ -86,7 +88,46 @@ struct bios_args { | |||
86 | struct bios_return { | 88 | struct bios_return { |
87 | u32 sigpass; | 89 | u32 sigpass; |
88 | u32 return_code; | 90 | u32 return_code; |
89 | u32 value; | 91 | }; |
92 | |||
93 | enum hp_return_value { | ||
94 | HPWMI_RET_WRONG_SIGNATURE = 0x02, | ||
95 | HPWMI_RET_UNKNOWN_COMMAND = 0x03, | ||
96 | HPWMI_RET_UNKNOWN_CMDTYPE = 0x04, | ||
97 | HPWMI_RET_INVALID_PARAMETERS = 0x05, | ||
98 | }; | ||
99 | |||
100 | enum hp_wireless2_bits { | ||
101 | HPWMI_POWER_STATE = 0x01, | ||
102 | HPWMI_POWER_SOFT = 0x02, | ||
103 | HPWMI_POWER_BIOS = 0x04, | ||
104 | HPWMI_POWER_HARD = 0x08, | ||
105 | }; | ||
106 | |||
107 | #define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \ | ||
108 | != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) | ||
109 | #define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT) | ||
110 | |||
111 | struct bios_rfkill2_device_state { | ||
112 | u8 radio_type; | ||
113 | u8 bus_type; | ||
114 | u16 vendor_id; | ||
115 | u16 product_id; | ||
116 | u16 subsys_vendor_id; | ||
117 | u16 subsys_product_id; | ||
118 | u8 rfkill_id; | ||
119 | u8 power; | ||
120 | u8 unknown[4]; | ||
121 | }; | ||
122 | |||
123 | /* 7 devices fit into the 128 byte buffer */ | ||
124 | #define HPWMI_MAX_RFKILL2_DEVICES 7 | ||
125 | |||
126 | struct bios_rfkill2_state { | ||
127 | u8 unknown[7]; | ||
128 | u8 count; | ||
129 | u8 pad[8]; | ||
130 | struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES]; | ||
90 | }; | 131 | }; |
91 | 132 | ||
92 | static const struct key_entry hp_wmi_keymap[] = { | 133 | static const struct key_entry hp_wmi_keymap[] = { |
@@ -108,6 +149,15 @@ static struct rfkill *wifi_rfkill; | |||
108 | static struct rfkill *bluetooth_rfkill; | 149 | static struct rfkill *bluetooth_rfkill; |
109 | static struct rfkill *wwan_rfkill; | 150 | static struct rfkill *wwan_rfkill; |
110 | 151 | ||
152 | struct rfkill2_device { | ||
153 | u8 id; | ||
154 | int num; | ||
155 | struct rfkill *rfkill; | ||
156 | }; | ||
157 | |||
158 | static int rfkill2_count; | ||
159 | static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; | ||
160 | |||
111 | static const struct dev_pm_ops hp_wmi_pm_ops = { | 161 | static const struct dev_pm_ops hp_wmi_pm_ops = { |
112 | .resume = hp_wmi_resume_handler, | 162 | .resume = hp_wmi_resume_handler, |
113 | .restore = hp_wmi_resume_handler, | 163 | .restore = hp_wmi_resume_handler, |
@@ -129,7 +179,8 @@ static struct platform_driver hp_wmi_driver = { | |||
129 | * query: The commandtype -> What should be queried | 179 | * query: The commandtype -> What should be queried |
130 | * write: The command -> 0 read, 1 write, 3 ODM specific | 180 | * write: The command -> 0 read, 1 write, 3 ODM specific |
131 | * buffer: Buffer used as input and/or output | 181 | * buffer: Buffer used as input and/or output |
132 | * buffersize: Size of buffer | 182 | * insize: Size of input buffer |
183 | * outsize: Size of output buffer | ||
133 | * | 184 | * |
134 | * returns zero on success | 185 | * returns zero on success |
135 | * an HP WMI query specific error code (which is positive) | 186 | * an HP WMI query specific error code (which is positive) |
@@ -140,25 +191,29 @@ static struct platform_driver hp_wmi_driver = { | |||
140 | * size. E.g. Battery info query (0x7) is defined to have 1 byte input | 191 | * size. E.g. Battery info query (0x7) is defined to have 1 byte input |
141 | * and 128 byte output. The caller would do: | 192 | * and 128 byte output. The caller would do: |
142 | * buffer = kzalloc(128, GFP_KERNEL); | 193 | * buffer = kzalloc(128, GFP_KERNEL); |
143 | * ret = hp_wmi_perform_query(0x7, 0, buffer, 128) | 194 | * ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128) |
144 | */ | 195 | */ |
145 | static int hp_wmi_perform_query(int query, int write, u32 *buffer, | 196 | static int hp_wmi_perform_query(int query, int write, void *buffer, |
146 | int buffersize) | 197 | int insize, int outsize) |
147 | { | 198 | { |
148 | struct bios_return bios_return; | 199 | struct bios_return *bios_return; |
149 | acpi_status status; | 200 | int actual_outsize; |
150 | union acpi_object *obj; | 201 | union acpi_object *obj; |
151 | struct bios_args args = { | 202 | struct bios_args args = { |
152 | .signature = 0x55434553, | 203 | .signature = 0x55434553, |
153 | .command = write ? 0x2 : 0x1, | 204 | .command = write ? 0x2 : 0x1, |
154 | .commandtype = query, | 205 | .commandtype = query, |
155 | .datasize = buffersize, | 206 | .datasize = insize, |
156 | .data = *buffer, | 207 | .data = 0, |
157 | }; | 208 | }; |
158 | struct acpi_buffer input = { sizeof(struct bios_args), &args }; | 209 | struct acpi_buffer input = { sizeof(struct bios_args), &args }; |
159 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | 210 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; |
160 | 211 | ||
161 | status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output); | 212 | if (WARN_ON(insize > sizeof(args.data))) |
213 | return -EINVAL; | ||
214 | memcpy(&args.data, buffer, insize); | ||
215 | |||
216 | wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output); | ||
162 | 217 | ||
163 | obj = output.pointer; | 218 | obj = output.pointer; |
164 | 219 | ||
@@ -169,10 +224,26 @@ static int hp_wmi_perform_query(int query, int write, u32 *buffer, | |||
169 | return -EINVAL; | 224 | return -EINVAL; |
170 | } | 225 | } |
171 | 226 | ||
172 | bios_return = *((struct bios_return *)obj->buffer.pointer); | 227 | bios_return = (struct bios_return *)obj->buffer.pointer; |
173 | 228 | ||
174 | memcpy(buffer, &bios_return.value, sizeof(bios_return.value)); | 229 | if (bios_return->return_code) { |
230 | if (bios_return->return_code != HPWMI_RET_UNKNOWN_CMDTYPE) | ||
231 | printk(KERN_WARNING PREFIX "query 0x%x returned " | ||
232 | "error 0x%x\n", | ||
233 | query, bios_return->return_code); | ||
234 | kfree(obj); | ||
235 | return bios_return->return_code; | ||
236 | } | ||
237 | |||
238 | if (!outsize) { | ||
239 | /* ignore output data */ | ||
240 | kfree(obj); | ||
241 | return 0; | ||
242 | } | ||
175 | 243 | ||
244 | actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return))); | ||
245 | memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize); | ||
246 | memset(buffer + actual_outsize, 0, outsize - actual_outsize); | ||
176 | kfree(obj); | 247 | kfree(obj); |
177 | return 0; | 248 | return 0; |
178 | } | 249 | } |
@@ -181,7 +252,7 @@ static int hp_wmi_display_state(void) | |||
181 | { | 252 | { |
182 | int state = 0; | 253 | int state = 0; |
183 | int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state, | 254 | int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state, |
184 | sizeof(state)); | 255 | sizeof(state), sizeof(state)); |
185 | if (ret) | 256 | if (ret) |
186 | return -EINVAL; | 257 | return -EINVAL; |
187 | return state; | 258 | return state; |
@@ -191,7 +262,7 @@ static int hp_wmi_hddtemp_state(void) | |||
191 | { | 262 | { |
192 | int state = 0; | 263 | int state = 0; |
193 | int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state, | 264 | int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state, |
194 | sizeof(state)); | 265 | sizeof(state), sizeof(state)); |
195 | if (ret) | 266 | if (ret) |
196 | return -EINVAL; | 267 | return -EINVAL; |
197 | return state; | 268 | return state; |
@@ -201,7 +272,7 @@ static int hp_wmi_als_state(void) | |||
201 | { | 272 | { |
202 | int state = 0; | 273 | int state = 0; |
203 | int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state, | 274 | int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state, |
204 | sizeof(state)); | 275 | sizeof(state), sizeof(state)); |
205 | if (ret) | 276 | if (ret) |
206 | return -EINVAL; | 277 | return -EINVAL; |
207 | return state; | 278 | return state; |
@@ -211,7 +282,7 @@ static int hp_wmi_dock_state(void) | |||
211 | { | 282 | { |
212 | int state = 0; | 283 | int state = 0; |
213 | int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, | 284 | int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, |
214 | sizeof(state)); | 285 | sizeof(state), sizeof(state)); |
215 | 286 | ||
216 | if (ret) | 287 | if (ret) |
217 | return -EINVAL; | 288 | return -EINVAL; |
@@ -223,7 +294,7 @@ static int hp_wmi_tablet_state(void) | |||
223 | { | 294 | { |
224 | int state = 0; | 295 | int state = 0; |
225 | int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, | 296 | int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, |
226 | sizeof(state)); | 297 | sizeof(state), sizeof(state)); |
227 | if (ret) | 298 | if (ret) |
228 | return ret; | 299 | return ret; |
229 | 300 | ||
@@ -237,7 +308,7 @@ static int hp_wmi_set_block(void *data, bool blocked) | |||
237 | int ret; | 308 | int ret; |
238 | 309 | ||
239 | ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, | 310 | ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, |
240 | &query, sizeof(query)); | 311 | &query, sizeof(query), 0); |
241 | if (ret) | 312 | if (ret) |
242 | return -EINVAL; | 313 | return -EINVAL; |
243 | return 0; | 314 | return 0; |
@@ -252,7 +323,8 @@ static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) | |||
252 | int wireless = 0; | 323 | int wireless = 0; |
253 | int mask; | 324 | int mask; |
254 | hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, | 325 | hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, |
255 | &wireless, sizeof(wireless)); | 326 | &wireless, sizeof(wireless), |
327 | sizeof(wireless)); | ||
256 | /* TBD: Pass error */ | 328 | /* TBD: Pass error */ |
257 | 329 | ||
258 | mask = 0x200 << (r * 8); | 330 | mask = 0x200 << (r * 8); |
@@ -268,7 +340,8 @@ static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) | |||
268 | int wireless = 0; | 340 | int wireless = 0; |
269 | int mask; | 341 | int mask; |
270 | hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, | 342 | hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, |
271 | &wireless, sizeof(wireless)); | 343 | &wireless, sizeof(wireless), |
344 | sizeof(wireless)); | ||
272 | /* TBD: Pass error */ | 345 | /* TBD: Pass error */ |
273 | 346 | ||
274 | mask = 0x800 << (r * 8); | 347 | mask = 0x800 << (r * 8); |
@@ -279,6 +352,51 @@ static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) | |||
279 | return true; | 352 | return true; |
280 | } | 353 | } |
281 | 354 | ||
355 | static int hp_wmi_rfkill2_set_block(void *data, bool blocked) | ||
356 | { | ||
357 | int rfkill_id = (int)(long)data; | ||
358 | char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked }; | ||
359 | |||
360 | if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1, | ||
361 | buffer, sizeof(buffer), 0)) | ||
362 | return -EINVAL; | ||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | static const struct rfkill_ops hp_wmi_rfkill2_ops = { | ||
367 | .set_block = hp_wmi_rfkill2_set_block, | ||
368 | }; | ||
369 | |||
370 | static int hp_wmi_rfkill2_refresh(void) | ||
371 | { | ||
372 | int err, i; | ||
373 | struct bios_rfkill2_state state; | ||
374 | |||
375 | err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state, | ||
376 | 0, sizeof(state)); | ||
377 | if (err) | ||
378 | return err; | ||
379 | |||
380 | for (i = 0; i < rfkill2_count; i++) { | ||
381 | int num = rfkill2[i].num; | ||
382 | struct bios_rfkill2_device_state *devstate; | ||
383 | devstate = &state.device[num]; | ||
384 | |||
385 | if (num >= state.count || | ||
386 | devstate->rfkill_id != rfkill2[i].id) { | ||
387 | printk(KERN_WARNING PREFIX "power configuration of " | ||
388 | "the wireless devices unexpectedly changed\n"); | ||
389 | continue; | ||
390 | } | ||
391 | |||
392 | rfkill_set_states(rfkill2[i].rfkill, | ||
393 | IS_SWBLOCKED(devstate->power), | ||
394 | IS_HWBLOCKED(devstate->power)); | ||
395 | } | ||
396 | |||
397 | return 0; | ||
398 | } | ||
399 | |||
282 | static ssize_t show_display(struct device *dev, struct device_attribute *attr, | 400 | static ssize_t show_display(struct device *dev, struct device_attribute *attr, |
283 | char *buf) | 401 | char *buf) |
284 | { | 402 | { |
@@ -329,7 +447,7 @@ static ssize_t set_als(struct device *dev, struct device_attribute *attr, | |||
329 | { | 447 | { |
330 | u32 tmp = simple_strtoul(buf, NULL, 10); | 448 | u32 tmp = simple_strtoul(buf, NULL, 10); |
331 | int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp, | 449 | int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp, |
332 | sizeof(tmp)); | 450 | sizeof(tmp), sizeof(tmp)); |
333 | if (ret) | 451 | if (ret) |
334 | return -EINVAL; | 452 | return -EINVAL; |
335 | 453 | ||
@@ -402,6 +520,7 @@ static void hp_wmi_notify(u32 value, void *context) | |||
402 | case HPWMI_BEZEL_BUTTON: | 520 | case HPWMI_BEZEL_BUTTON: |
403 | ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, | 521 | ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, |
404 | &key_code, | 522 | &key_code, |
523 | sizeof(key_code), | ||
405 | sizeof(key_code)); | 524 | sizeof(key_code)); |
406 | if (ret) | 525 | if (ret) |
407 | break; | 526 | break; |
@@ -412,6 +531,11 @@ static void hp_wmi_notify(u32 value, void *context) | |||
412 | key_code); | 531 | key_code); |
413 | break; | 532 | break; |
414 | case HPWMI_WIRELESS: | 533 | case HPWMI_WIRELESS: |
534 | if (rfkill2_count) { | ||
535 | hp_wmi_rfkill2_refresh(); | ||
536 | break; | ||
537 | } | ||
538 | |||
415 | if (wifi_rfkill) | 539 | if (wifi_rfkill) |
416 | rfkill_set_states(wifi_rfkill, | 540 | rfkill_set_states(wifi_rfkill, |
417 | hp_wmi_get_sw_state(HPWMI_WIFI), | 541 | hp_wmi_get_sw_state(HPWMI_WIFI), |
@@ -502,32 +626,16 @@ static void cleanup_sysfs(struct platform_device *device) | |||
502 | device_remove_file(&device->dev, &dev_attr_tablet); | 626 | device_remove_file(&device->dev, &dev_attr_tablet); |
503 | } | 627 | } |
504 | 628 | ||
505 | static int __devinit hp_wmi_bios_setup(struct platform_device *device) | 629 | static int __devinit hp_wmi_rfkill_setup(struct platform_device *device) |
506 | { | 630 | { |
507 | int err; | 631 | int err; |
508 | int wireless = 0; | 632 | int wireless = 0; |
509 | 633 | ||
510 | err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless, | 634 | err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless, |
511 | sizeof(wireless)); | 635 | sizeof(wireless), sizeof(wireless)); |
512 | if (err) | 636 | if (err) |
513 | return err; | 637 | return err; |
514 | 638 | ||
515 | err = device_create_file(&device->dev, &dev_attr_display); | ||
516 | if (err) | ||
517 | goto add_sysfs_error; | ||
518 | err = device_create_file(&device->dev, &dev_attr_hddtemp); | ||
519 | if (err) | ||
520 | goto add_sysfs_error; | ||
521 | err = device_create_file(&device->dev, &dev_attr_als); | ||
522 | if (err) | ||
523 | goto add_sysfs_error; | ||
524 | err = device_create_file(&device->dev, &dev_attr_dock); | ||
525 | if (err) | ||
526 | goto add_sysfs_error; | ||
527 | err = device_create_file(&device->dev, &dev_attr_tablet); | ||
528 | if (err) | ||
529 | goto add_sysfs_error; | ||
530 | |||
531 | if (wireless & 0x1) { | 639 | if (wireless & 0x1) { |
532 | wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, | 640 | wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, |
533 | RFKILL_TYPE_WLAN, | 641 | RFKILL_TYPE_WLAN, |
@@ -573,14 +681,131 @@ static int __devinit hp_wmi_bios_setup(struct platform_device *device) | |||
573 | return 0; | 681 | return 0; |
574 | register_wwan_err: | 682 | register_wwan_err: |
575 | rfkill_destroy(wwan_rfkill); | 683 | rfkill_destroy(wwan_rfkill); |
684 | wwan_rfkill = NULL; | ||
576 | if (bluetooth_rfkill) | 685 | if (bluetooth_rfkill) |
577 | rfkill_unregister(bluetooth_rfkill); | 686 | rfkill_unregister(bluetooth_rfkill); |
578 | register_bluetooth_error: | 687 | register_bluetooth_error: |
579 | rfkill_destroy(bluetooth_rfkill); | 688 | rfkill_destroy(bluetooth_rfkill); |
689 | bluetooth_rfkill = NULL; | ||
580 | if (wifi_rfkill) | 690 | if (wifi_rfkill) |
581 | rfkill_unregister(wifi_rfkill); | 691 | rfkill_unregister(wifi_rfkill); |
582 | register_wifi_error: | 692 | register_wifi_error: |
583 | rfkill_destroy(wifi_rfkill); | 693 | rfkill_destroy(wifi_rfkill); |
694 | wifi_rfkill = NULL; | ||
695 | return err; | ||
696 | } | ||
697 | |||
698 | static int __devinit hp_wmi_rfkill2_setup(struct platform_device *device) | ||
699 | { | ||
700 | int err, i; | ||
701 | struct bios_rfkill2_state state; | ||
702 | err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state, | ||
703 | 0, sizeof(state)); | ||
704 | if (err) | ||
705 | return err; | ||
706 | |||
707 | if (state.count > HPWMI_MAX_RFKILL2_DEVICES) { | ||
708 | printk(KERN_WARNING PREFIX "unable to parse 0x1b query output\n"); | ||
709 | return -EINVAL; | ||
710 | } | ||
711 | |||
712 | for (i = 0; i < state.count; i++) { | ||
713 | struct rfkill *rfkill; | ||
714 | enum rfkill_type type; | ||
715 | char *name; | ||
716 | switch (state.device[i].radio_type) { | ||
717 | case HPWMI_WIFI: | ||
718 | type = RFKILL_TYPE_WLAN; | ||
719 | name = "hp-wifi"; | ||
720 | break; | ||
721 | case HPWMI_BLUETOOTH: | ||
722 | type = RFKILL_TYPE_BLUETOOTH; | ||
723 | name = "hp-bluetooth"; | ||
724 | break; | ||
725 | case HPWMI_WWAN: | ||
726 | type = RFKILL_TYPE_WWAN; | ||
727 | name = "hp-wwan"; | ||
728 | break; | ||
729 | default: | ||
730 | printk(KERN_WARNING PREFIX "unknown device type 0x%x\n", | ||
731 | state.device[i].radio_type); | ||
732 | continue; | ||
733 | } | ||
734 | |||
735 | if (!state.device[i].vendor_id) { | ||
736 | printk(KERN_WARNING PREFIX "zero device %d while %d " | ||
737 | "reported\n", i, state.count); | ||
738 | continue; | ||
739 | } | ||
740 | |||
741 | rfkill = rfkill_alloc(name, &device->dev, type, | ||
742 | &hp_wmi_rfkill2_ops, (void *)(long)i); | ||
743 | if (!rfkill) { | ||
744 | err = -ENOMEM; | ||
745 | goto fail; | ||
746 | } | ||
747 | |||
748 | rfkill2[rfkill2_count].id = state.device[i].rfkill_id; | ||
749 | rfkill2[rfkill2_count].num = i; | ||
750 | rfkill2[rfkill2_count].rfkill = rfkill; | ||
751 | |||
752 | rfkill_init_sw_state(rfkill, | ||
753 | IS_SWBLOCKED(state.device[i].power)); | ||
754 | rfkill_set_hw_state(rfkill, | ||
755 | IS_HWBLOCKED(state.device[i].power)); | ||
756 | |||
757 | if (!(state.device[i].power & HPWMI_POWER_BIOS)) | ||
758 | printk(KERN_INFO PREFIX "device %s blocked by BIOS\n", | ||
759 | name); | ||
760 | |||
761 | err = rfkill_register(rfkill); | ||
762 | if (err) { | ||
763 | rfkill_destroy(rfkill); | ||
764 | goto fail; | ||
765 | } | ||
766 | |||
767 | rfkill2_count++; | ||
768 | } | ||
769 | |||
770 | return 0; | ||
771 | fail: | ||
772 | for (; rfkill2_count > 0; rfkill2_count--) { | ||
773 | rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill); | ||
774 | rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill); | ||
775 | } | ||
776 | return err; | ||
777 | } | ||
778 | |||
779 | static int __devinit hp_wmi_bios_setup(struct platform_device *device) | ||
780 | { | ||
781 | int err; | ||
782 | |||
783 | /* clear detected rfkill devices */ | ||
784 | wifi_rfkill = NULL; | ||
785 | bluetooth_rfkill = NULL; | ||
786 | wwan_rfkill = NULL; | ||
787 | rfkill2_count = 0; | ||
788 | |||
789 | if (hp_wmi_rfkill_setup(device)) | ||
790 | hp_wmi_rfkill2_setup(device); | ||
791 | |||
792 | err = device_create_file(&device->dev, &dev_attr_display); | ||
793 | if (err) | ||
794 | goto add_sysfs_error; | ||
795 | err = device_create_file(&device->dev, &dev_attr_hddtemp); | ||
796 | if (err) | ||
797 | goto add_sysfs_error; | ||
798 | err = device_create_file(&device->dev, &dev_attr_als); | ||
799 | if (err) | ||
800 | goto add_sysfs_error; | ||
801 | err = device_create_file(&device->dev, &dev_attr_dock); | ||
802 | if (err) | ||
803 | goto add_sysfs_error; | ||
804 | err = device_create_file(&device->dev, &dev_attr_tablet); | ||
805 | if (err) | ||
806 | goto add_sysfs_error; | ||
807 | return 0; | ||
808 | |||
584 | add_sysfs_error: | 809 | add_sysfs_error: |
585 | cleanup_sysfs(device); | 810 | cleanup_sysfs(device); |
586 | return err; | 811 | return err; |
@@ -588,8 +813,14 @@ add_sysfs_error: | |||
588 | 813 | ||
589 | static int __exit hp_wmi_bios_remove(struct platform_device *device) | 814 | static int __exit hp_wmi_bios_remove(struct platform_device *device) |
590 | { | 815 | { |
816 | int i; | ||
591 | cleanup_sysfs(device); | 817 | cleanup_sysfs(device); |
592 | 818 | ||
819 | for (i = 0; i < rfkill2_count; i++) { | ||
820 | rfkill_unregister(rfkill2[i].rfkill); | ||
821 | rfkill_destroy(rfkill2[i].rfkill); | ||
822 | } | ||
823 | |||
593 | if (wifi_rfkill) { | 824 | if (wifi_rfkill) { |
594 | rfkill_unregister(wifi_rfkill); | 825 | rfkill_unregister(wifi_rfkill); |
595 | rfkill_destroy(wifi_rfkill); | 826 | rfkill_destroy(wifi_rfkill); |
@@ -622,6 +853,9 @@ static int hp_wmi_resume_handler(struct device *device) | |||
622 | input_sync(hp_wmi_input_dev); | 853 | input_sync(hp_wmi_input_dev); |
623 | } | 854 | } |
624 | 855 | ||
856 | if (rfkill2_count) | ||
857 | hp_wmi_rfkill2_refresh(); | ||
858 | |||
625 | if (wifi_rfkill) | 859 | if (wifi_rfkill) |
626 | rfkill_set_states(wifi_rfkill, | 860 | rfkill_set_states(wifi_rfkill, |
627 | hp_wmi_get_sw_state(HPWMI_WIFI), | 861 | hp_wmi_get_sw_state(HPWMI_WIFI), |
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 114d95247cdf..21b101899bae 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c | |||
@@ -459,6 +459,8 @@ static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event) | |||
459 | if (test_bit(vpc_bit, &vpc1)) { | 459 | if (test_bit(vpc_bit, &vpc1)) { |
460 | if (vpc_bit == 9) | 460 | if (vpc_bit == 9) |
461 | ideapad_sync_rfk_state(adevice); | 461 | ideapad_sync_rfk_state(adevice); |
462 | else if (vpc_bit == 4) | ||
463 | read_ec_data(handle, 0x12, &vpc2); | ||
462 | else | 464 | else |
463 | ideapad_input_report(priv, vpc_bit); | 465 | ideapad_input_report(priv, vpc_bit); |
464 | } | 466 | } |
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index 1294a39373ba..85c8ad43c0c5 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c | |||
@@ -1111,7 +1111,7 @@ static int ips_monitor(void *data) | |||
1111 | last_msecs = jiffies_to_msecs(jiffies); | 1111 | last_msecs = jiffies_to_msecs(jiffies); |
1112 | expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); | 1112 | expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); |
1113 | 1113 | ||
1114 | __set_current_state(TASK_UNINTERRUPTIBLE); | 1114 | __set_current_state(TASK_INTERRUPTIBLE); |
1115 | mod_timer(&timer, expire); | 1115 | mod_timer(&timer, expire); |
1116 | schedule(); | 1116 | schedule(); |
1117 | 1117 | ||
diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c new file mode 100644 index 000000000000..213e79ba68d5 --- /dev/null +++ b/drivers/platform/x86/intel_mid_powerbtn.c | |||
@@ -0,0 +1,148 @@ | |||
1 | /* | ||
2 | * Power button driver for Medfield. | ||
3 | * | ||
4 | * Copyright (C) 2010 Intel Corp | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; version 2 of the License. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but | ||
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | * General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along | ||
16 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/input.h> | ||
26 | #include <asm/intel_scu_ipc.h> | ||
27 | |||
28 | #define DRIVER_NAME "msic_power_btn" | ||
29 | |||
30 | #define MSIC_IRQ_STAT 0x02 | ||
31 | #define MSIC_IRQ_PB (1 << 0) | ||
32 | #define MSIC_PB_CONFIG 0x3e | ||
33 | #define MSIC_PB_STATUS 0x3f | ||
34 | #define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ | ||
35 | |||
36 | struct mfld_pb_priv { | ||
37 | struct input_dev *input; | ||
38 | unsigned int irq; | ||
39 | }; | ||
40 | |||
41 | static irqreturn_t mfld_pb_isr(int irq, void *dev_id) | ||
42 | { | ||
43 | struct mfld_pb_priv *priv = dev_id; | ||
44 | int ret; | ||
45 | u8 pbstat; | ||
46 | |||
47 | ret = intel_scu_ipc_ioread8(MSIC_PB_STATUS, &pbstat); | ||
48 | if (ret < 0) | ||
49 | return IRQ_HANDLED; | ||
50 | |||
51 | input_event(priv->input, EV_KEY, KEY_POWER, !(pbstat & MSIC_PB_LEVEL)); | ||
52 | input_sync(priv->input); | ||
53 | |||
54 | return IRQ_HANDLED; | ||
55 | } | ||
56 | |||
57 | static int __devinit mfld_pb_probe(struct platform_device *pdev) | ||
58 | { | ||
59 | struct mfld_pb_priv *priv; | ||
60 | struct input_dev *input; | ||
61 | int irq; | ||
62 | int error; | ||
63 | |||
64 | irq = platform_get_irq(pdev, 0); | ||
65 | if (irq < 0) | ||
66 | return -EINVAL; | ||
67 | |||
68 | priv = kzalloc(sizeof(struct mfld_pb_priv), GFP_KERNEL); | ||
69 | input = input_allocate_device(); | ||
70 | if (!priv || !input) { | ||
71 | error = -ENOMEM; | ||
72 | goto err_free_mem; | ||
73 | } | ||
74 | |||
75 | priv->input = input; | ||
76 | priv->irq = irq; | ||
77 | |||
78 | input->name = pdev->name; | ||
79 | input->phys = "power-button/input0"; | ||
80 | input->id.bustype = BUS_HOST; | ||
81 | input->dev.parent = &pdev->dev; | ||
82 | |||
83 | input_set_capability(input, EV_KEY, KEY_POWER); | ||
84 | |||
85 | error = request_threaded_irq(priv->irq, NULL, mfld_pb_isr, | ||
86 | 0, DRIVER_NAME, priv); | ||
87 | if (error) { | ||
88 | dev_err(&pdev->dev, | ||
89 | "unable to request irq %d for mfld power button\n", | ||
90 | irq); | ||
91 | goto err_free_mem; | ||
92 | } | ||
93 | |||
94 | error = input_register_device(input); | ||
95 | if (error) { | ||
96 | dev_err(&pdev->dev, | ||
97 | "unable to register input dev, error %d\n", error); | ||
98 | goto err_free_irq; | ||
99 | } | ||
100 | |||
101 | platform_set_drvdata(pdev, priv); | ||
102 | return 0; | ||
103 | |||
104 | err_free_irq: | ||
105 | free_irq(priv->irq, priv); | ||
106 | err_free_mem: | ||
107 | input_free_device(input); | ||
108 | kfree(priv); | ||
109 | return error; | ||
110 | } | ||
111 | |||
112 | static int __devexit mfld_pb_remove(struct platform_device *pdev) | ||
113 | { | ||
114 | struct mfld_pb_priv *priv = platform_get_drvdata(pdev); | ||
115 | |||
116 | free_irq(priv->irq, priv); | ||
117 | input_unregister_device(priv->input); | ||
118 | kfree(priv); | ||
119 | |||
120 | platform_set_drvdata(pdev, NULL); | ||
121 | return 0; | ||
122 | } | ||
123 | |||
124 | static struct platform_driver mfld_pb_driver = { | ||
125 | .driver = { | ||
126 | .name = DRIVER_NAME, | ||
127 | .owner = THIS_MODULE, | ||
128 | }, | ||
129 | .probe = mfld_pb_probe, | ||
130 | .remove = __devexit_p(mfld_pb_remove), | ||
131 | }; | ||
132 | |||
133 | static int __init mfld_pb_init(void) | ||
134 | { | ||
135 | return platform_driver_register(&mfld_pb_driver); | ||
136 | } | ||
137 | module_init(mfld_pb_init); | ||
138 | |||
139 | static void __exit mfld_pb_exit(void) | ||
140 | { | ||
141 | platform_driver_unregister(&mfld_pb_driver); | ||
142 | } | ||
143 | module_exit(mfld_pb_exit); | ||
144 | |||
145 | MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>"); | ||
146 | MODULE_DESCRIPTION("Intel Medfield Power Button Driver"); | ||
147 | MODULE_LICENSE("GPL v2"); | ||
148 | MODULE_ALIAS("platform:" DRIVER_NAME); | ||
diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c new file mode 100644 index 000000000000..6c12db503161 --- /dev/null +++ b/drivers/platform/x86/intel_mid_thermal.c | |||
@@ -0,0 +1,576 @@ | |||
1 | /* | ||
2 | * intel_mid_thermal.c - Intel MID platform thermal driver | ||
3 | * | ||
4 | * Copyright (C) 2011 Intel Corporation | ||
5 | * | ||
6 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
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; version 2 of the License. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License along | ||
18 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
19 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | ||
20 | * | ||
21 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
22 | * Author: Durgadoss R <durgadoss.r@intel.com> | ||
23 | */ | ||
24 | |||
25 | #define pr_fmt(fmt) "intel_mid_thermal: " fmt | ||
26 | |||
27 | #include <linux/module.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/err.h> | ||
30 | #include <linux/param.h> | ||
31 | #include <linux/device.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/pm.h> | ||
35 | #include <linux/thermal.h> | ||
36 | |||
37 | #include <asm/intel_scu_ipc.h> | ||
38 | |||
39 | /* Number of thermal sensors */ | ||
40 | #define MSIC_THERMAL_SENSORS 4 | ||
41 | |||
42 | /* ADC1 - thermal registers */ | ||
43 | #define MSIC_THERM_ADC1CNTL1 0x1C0 | ||
44 | #define MSIC_ADC_ENBL 0x10 | ||
45 | #define MSIC_ADC_START 0x08 | ||
46 | |||
47 | #define MSIC_THERM_ADC1CNTL3 0x1C2 | ||
48 | #define MSIC_ADCTHERM_ENBL 0x04 | ||
49 | #define MSIC_ADCRRDATA_ENBL 0x05 | ||
50 | #define MSIC_CHANL_MASK_VAL 0x0F | ||
51 | |||
52 | #define MSIC_STOPBIT_MASK 16 | ||
53 | #define MSIC_ADCTHERM_MASK 4 | ||
54 | #define ADC_CHANLS_MAX 15 /* Number of ADC channels */ | ||
55 | #define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) | ||
56 | |||
57 | /* ADC channel code values */ | ||
58 | #define SKIN_SENSOR0_CODE 0x08 | ||
59 | #define SKIN_SENSOR1_CODE 0x09 | ||
60 | #define SYS_SENSOR_CODE 0x0A | ||
61 | #define MSIC_DIE_SENSOR_CODE 0x03 | ||
62 | |||
63 | #define SKIN_THERM_SENSOR0 0 | ||
64 | #define SKIN_THERM_SENSOR1 1 | ||
65 | #define SYS_THERM_SENSOR2 2 | ||
66 | #define MSIC_DIE_THERM_SENSOR3 3 | ||
67 | |||
68 | /* ADC code range */ | ||
69 | #define ADC_MAX 977 | ||
70 | #define ADC_MIN 162 | ||
71 | #define ADC_VAL0C 887 | ||
72 | #define ADC_VAL20C 720 | ||
73 | #define ADC_VAL40C 508 | ||
74 | #define ADC_VAL60C 315 | ||
75 | |||
76 | /* ADC base addresses */ | ||
77 | #define ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ | ||
78 | #define ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ | ||
79 | |||
80 | /* MSIC die attributes */ | ||
81 | #define MSIC_DIE_ADC_MIN 488 | ||
82 | #define MSIC_DIE_ADC_MAX 1004 | ||
83 | |||
84 | /* This holds the address of the first free ADC channel, | ||
85 | * among the 15 channels | ||
86 | */ | ||
87 | static int channel_index; | ||
88 | |||
89 | struct platform_info { | ||
90 | struct platform_device *pdev; | ||
91 | struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; | ||
92 | }; | ||
93 | |||
94 | struct thermal_device_info { | ||
95 | unsigned int chnl_addr; | ||
96 | int direct; | ||
97 | /* This holds the current temperature in millidegree celsius */ | ||
98 | long curr_temp; | ||
99 | }; | ||
100 | |||
101 | /** | ||
102 | * to_msic_die_temp - converts adc_val to msic_die temperature | ||
103 | * @adc_val: ADC value to be converted | ||
104 | * | ||
105 | * Can sleep | ||
106 | */ | ||
107 | static int to_msic_die_temp(uint16_t adc_val) | ||
108 | { | ||
109 | return (368 * (adc_val) / 1000) - 220; | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * is_valid_adc - checks whether the adc code is within the defined range | ||
114 | * @min: minimum value for the sensor | ||
115 | * @max: maximum value for the sensor | ||
116 | * | ||
117 | * Can sleep | ||
118 | */ | ||
119 | static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) | ||
120 | { | ||
121 | return (adc_val >= min) && (adc_val <= max); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * adc_to_temp - converts the ADC code to temperature in C | ||
126 | * @direct: true if ths channel is direct index | ||
127 | * @adc_val: the adc_val that needs to be converted | ||
128 | * @tp: temperature return value | ||
129 | * | ||
130 | * Linear approximation is used to covert the skin adc value into temperature. | ||
131 | * This technique is used to avoid very long look-up table to get | ||
132 | * the appropriate temp value from ADC value. | ||
133 | * The adc code vs sensor temp curve is split into five parts | ||
134 | * to achieve very close approximate temp value with less than | ||
135 | * 0.5C error | ||
136 | */ | ||
137 | static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) | ||
138 | { | ||
139 | int temp; | ||
140 | |||
141 | /* Direct conversion for die temperature */ | ||
142 | if (direct) { | ||
143 | if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { | ||
144 | *tp = to_msic_die_temp(adc_val) * 1000; | ||
145 | return 0; | ||
146 | } | ||
147 | return -ERANGE; | ||
148 | } | ||
149 | |||
150 | if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) | ||
151 | return -ERANGE; | ||
152 | |||
153 | /* Linear approximation for skin temperature */ | ||
154 | if (adc_val > ADC_VAL0C) | ||
155 | temp = 177 - (adc_val/5); | ||
156 | else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) | ||
157 | temp = 111 - (adc_val/8); | ||
158 | else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) | ||
159 | temp = 92 - (adc_val/10); | ||
160 | else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) | ||
161 | temp = 91 - (adc_val/10); | ||
162 | else | ||
163 | temp = 112 - (adc_val/6); | ||
164 | |||
165 | /* Convert temperature in celsius to milli degree celsius */ | ||
166 | *tp = temp * 1000; | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * mid_read_temp - read sensors for temperature | ||
172 | * @temp: holds the current temperature for the sensor after reading | ||
173 | * | ||
174 | * reads the adc_code from the channel and converts it to real | ||
175 | * temperature. The converted value is stored in temp. | ||
176 | * | ||
177 | * Can sleep | ||
178 | */ | ||
179 | static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) | ||
180 | { | ||
181 | struct thermal_device_info *td_info = tzd->devdata; | ||
182 | uint16_t adc_val, addr; | ||
183 | uint8_t data = 0; | ||
184 | int ret; | ||
185 | unsigned long curr_temp; | ||
186 | |||
187 | |||
188 | addr = td_info->chnl_addr; | ||
189 | |||
190 | /* Enable the msic for conversion before reading */ | ||
191 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); | ||
192 | if (ret) | ||
193 | return ret; | ||
194 | |||
195 | /* Re-toggle the RRDATARD bit (temporary workaround) */ | ||
196 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCTHERM_ENBL); | ||
197 | if (ret) | ||
198 | return ret; | ||
199 | |||
200 | /* Read the higher bits of data */ | ||
201 | ret = intel_scu_ipc_ioread8(addr, &data); | ||
202 | if (ret) | ||
203 | return ret; | ||
204 | |||
205 | /* Shift bits to accomodate the lower two data bits */ | ||
206 | adc_val = (data << 2); | ||
207 | addr++; | ||
208 | |||
209 | ret = intel_scu_ipc_ioread8(addr, &data);/* Read lower bits */ | ||
210 | if (ret) | ||
211 | return ret; | ||
212 | |||
213 | /* Adding lower two bits to the higher bits */ | ||
214 | data &= 03; | ||
215 | adc_val += data; | ||
216 | |||
217 | /* Convert ADC value to temperature */ | ||
218 | ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); | ||
219 | if (ret == 0) | ||
220 | *temp = td_info->curr_temp = curr_temp; | ||
221 | return ret; | ||
222 | } | ||
223 | |||
224 | /** | ||
225 | * configure_adc - enables/disables the ADC for conversion | ||
226 | * @val: zero: disables the ADC non-zero:enables the ADC | ||
227 | * | ||
228 | * Enable/Disable the ADC depending on the argument | ||
229 | * | ||
230 | * Can sleep | ||
231 | */ | ||
232 | static int configure_adc(int val) | ||
233 | { | ||
234 | int ret; | ||
235 | uint8_t data; | ||
236 | |||
237 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL1, &data); | ||
238 | if (ret) | ||
239 | return ret; | ||
240 | |||
241 | if (val) { | ||
242 | /* Enable and start the ADC */ | ||
243 | data |= (MSIC_ADC_ENBL | MSIC_ADC_START); | ||
244 | } else { | ||
245 | /* Just stop the ADC */ | ||
246 | data &= (~MSIC_ADC_START); | ||
247 | } | ||
248 | |||
249 | return intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL1, data); | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * set_up_therm_channel - enable thermal channel for conversion | ||
254 | * @base_addr: index of free msic ADC channel | ||
255 | * | ||
256 | * Enable all the three channels for conversion | ||
257 | * | ||
258 | * Can sleep | ||
259 | */ | ||
260 | static int set_up_therm_channel(u16 base_addr) | ||
261 | { | ||
262 | int ret; | ||
263 | |||
264 | /* Enable all the sensor channels */ | ||
265 | ret = intel_scu_ipc_iowrite8(base_addr, SKIN_SENSOR0_CODE); | ||
266 | if (ret) | ||
267 | return ret; | ||
268 | |||
269 | ret = intel_scu_ipc_iowrite8(base_addr + 1, SKIN_SENSOR1_CODE); | ||
270 | if (ret) | ||
271 | return ret; | ||
272 | |||
273 | ret = intel_scu_ipc_iowrite8(base_addr + 2, SYS_SENSOR_CODE); | ||
274 | if (ret) | ||
275 | return ret; | ||
276 | |||
277 | /* Since this is the last channel, set the stop bit | ||
278 | to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ | ||
279 | ret = intel_scu_ipc_iowrite8(base_addr + 3, | ||
280 | (MSIC_DIE_SENSOR_CODE | 0x10)); | ||
281 | if (ret) | ||
282 | return ret; | ||
283 | |||
284 | /* Enable ADC and start it */ | ||
285 | return configure_adc(1); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * reset_stopbit - sets the stop bit to 0 on the given channel | ||
290 | * @addr: address of the channel | ||
291 | * | ||
292 | * Can sleep | ||
293 | */ | ||
294 | static int reset_stopbit(uint16_t addr) | ||
295 | { | ||
296 | int ret; | ||
297 | uint8_t data; | ||
298 | ret = intel_scu_ipc_ioread8(addr, &data); | ||
299 | if (ret) | ||
300 | return ret; | ||
301 | /* Set the stop bit to zero */ | ||
302 | return intel_scu_ipc_iowrite8(addr, (data & 0xEF)); | ||
303 | } | ||
304 | |||
305 | /** | ||
306 | * find_free_channel - finds an empty channel for conversion | ||
307 | * | ||
308 | * If the ADC is not enabled then start using 0th channel | ||
309 | * itself. Otherwise find an empty channel by looking for a | ||
310 | * channel in which the stopbit is set to 1. returns the index | ||
311 | * of the first free channel if succeeds or an error code. | ||
312 | * | ||
313 | * Context: can sleep | ||
314 | * | ||
315 | * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc | ||
316 | * code. | ||
317 | */ | ||
318 | static int find_free_channel(void) | ||
319 | { | ||
320 | int ret; | ||
321 | int i; | ||
322 | uint8_t data; | ||
323 | |||
324 | /* check whether ADC is enabled */ | ||
325 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL1, &data); | ||
326 | if (ret) | ||
327 | return ret; | ||
328 | |||
329 | if ((data & MSIC_ADC_ENBL) == 0) | ||
330 | return 0; | ||
331 | |||
332 | /* ADC is already enabled; Looking for an empty channel */ | ||
333 | for (i = 0; i < ADC_CHANLS_MAX; i++) { | ||
334 | ret = intel_scu_ipc_ioread8(ADC_CHNL_START_ADDR + i, &data); | ||
335 | if (ret) | ||
336 | return ret; | ||
337 | |||
338 | if (data & MSIC_STOPBIT_MASK) { | ||
339 | ret = i; | ||
340 | break; | ||
341 | } | ||
342 | } | ||
343 | return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * mid_initialize_adc - initializing the ADC | ||
348 | * @dev: our device structure | ||
349 | * | ||
350 | * Initialize the ADC for reading thermistor values. Can sleep. | ||
351 | */ | ||
352 | static int mid_initialize_adc(struct device *dev) | ||
353 | { | ||
354 | u8 data; | ||
355 | u16 base_addr; | ||
356 | int ret; | ||
357 | |||
358 | /* | ||
359 | * Ensure that adctherm is disabled before we | ||
360 | * initialize the ADC | ||
361 | */ | ||
362 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL3, &data); | ||
363 | if (ret) | ||
364 | return ret; | ||
365 | |||
366 | if (data & MSIC_ADCTHERM_MASK) | ||
367 | dev_warn(dev, "ADCTHERM already set"); | ||
368 | |||
369 | /* Index of the first channel in which the stop bit is set */ | ||
370 | channel_index = find_free_channel(); | ||
371 | if (channel_index < 0) { | ||
372 | dev_err(dev, "No free ADC channels"); | ||
373 | return channel_index; | ||
374 | } | ||
375 | |||
376 | base_addr = ADC_CHNL_START_ADDR + channel_index; | ||
377 | |||
378 | if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { | ||
379 | /* Reset stop bit for channels other than 0 and 12 */ | ||
380 | ret = reset_stopbit(base_addr); | ||
381 | if (ret) | ||
382 | return ret; | ||
383 | |||
384 | /* Index of the first free channel */ | ||
385 | base_addr++; | ||
386 | channel_index++; | ||
387 | } | ||
388 | |||
389 | ret = set_up_therm_channel(base_addr); | ||
390 | if (ret) { | ||
391 | dev_err(dev, "unable to enable ADC"); | ||
392 | return ret; | ||
393 | } | ||
394 | dev_dbg(dev, "ADC initialization successful"); | ||
395 | return ret; | ||
396 | } | ||
397 | |||
398 | /** | ||
399 | * initialize_sensor - sets default temp and timer ranges | ||
400 | * @index: index of the sensor | ||
401 | * | ||
402 | * Context: can sleep | ||
403 | */ | ||
404 | static struct thermal_device_info *initialize_sensor(int index) | ||
405 | { | ||
406 | struct thermal_device_info *td_info = | ||
407 | kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); | ||
408 | |||
409 | if (!td_info) | ||
410 | return NULL; | ||
411 | |||
412 | /* Set the base addr of the channel for this sensor */ | ||
413 | td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); | ||
414 | /* Sensor 3 is direct conversion */ | ||
415 | if (index == 3) | ||
416 | td_info->direct = 1; | ||
417 | return td_info; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * mid_thermal_resume - resume routine | ||
422 | * @pdev: platform device structure | ||
423 | * | ||
424 | * mid thermal resume: re-initializes the adc. Can sleep. | ||
425 | */ | ||
426 | static int mid_thermal_resume(struct platform_device *pdev) | ||
427 | { | ||
428 | return mid_initialize_adc(&pdev->dev); | ||
429 | } | ||
430 | |||
431 | /** | ||
432 | * mid_thermal_suspend - suspend routine | ||
433 | * @pdev: platform device structure | ||
434 | * | ||
435 | * mid thermal suspend implements the suspend functionality | ||
436 | * by stopping the ADC. Can sleep. | ||
437 | */ | ||
438 | static int mid_thermal_suspend(struct platform_device *pdev, pm_message_t mesg) | ||
439 | { | ||
440 | /* | ||
441 | * This just stops the ADC and does not disable it. | ||
442 | * temporary workaround until we have a generic ADC driver. | ||
443 | * If 0 is passed, it disables the ADC. | ||
444 | */ | ||
445 | return configure_adc(0); | ||
446 | } | ||
447 | |||
448 | /** | ||
449 | * read_curr_temp - reads the current temperature and stores in temp | ||
450 | * @temp: holds the current temperature value after reading | ||
451 | * | ||
452 | * Can sleep | ||
453 | */ | ||
454 | static int read_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp) | ||
455 | { | ||
456 | WARN_ON(tzd == NULL); | ||
457 | return mid_read_temp(tzd, temp); | ||
458 | } | ||
459 | |||
460 | /* Can't be const */ | ||
461 | static struct thermal_zone_device_ops tzd_ops = { | ||
462 | .get_temp = read_curr_temp, | ||
463 | }; | ||
464 | |||
465 | |||
466 | /** | ||
467 | * mid_thermal_probe - mfld thermal initialize | ||
468 | * @pdev: platform device structure | ||
469 | * | ||
470 | * mid thermal probe initializes the hardware and registers | ||
471 | * all the sensors with the generic thermal framework. Can sleep. | ||
472 | */ | ||
473 | static int mid_thermal_probe(struct platform_device *pdev) | ||
474 | { | ||
475 | static char *name[MSIC_THERMAL_SENSORS] = { | ||
476 | "skin0", "skin1", "sys", "msicdie" | ||
477 | }; | ||
478 | |||
479 | int ret; | ||
480 | int i; | ||
481 | struct platform_info *pinfo; | ||
482 | |||
483 | pinfo = kzalloc(sizeof(struct platform_info), GFP_KERNEL); | ||
484 | if (!pinfo) | ||
485 | return -ENOMEM; | ||
486 | |||
487 | /* Initializing the hardware */ | ||
488 | ret = mid_initialize_adc(&pdev->dev); | ||
489 | if (ret) { | ||
490 | dev_err(&pdev->dev, "ADC init failed"); | ||
491 | kfree(pinfo); | ||
492 | return ret; | ||
493 | } | ||
494 | |||
495 | /* Register each sensor with the generic thermal framework*/ | ||
496 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { | ||
497 | pinfo->tzd[i] = thermal_zone_device_register(name[i], | ||
498 | 0, initialize_sensor(i), | ||
499 | &tzd_ops, 0, 0, 0, 0); | ||
500 | if (IS_ERR(pinfo->tzd[i])) | ||
501 | goto reg_fail; | ||
502 | } | ||
503 | |||
504 | pinfo->pdev = pdev; | ||
505 | platform_set_drvdata(pdev, pinfo); | ||
506 | return 0; | ||
507 | |||
508 | reg_fail: | ||
509 | ret = PTR_ERR(pinfo->tzd[i]); | ||
510 | while (--i >= 0) | ||
511 | thermal_zone_device_unregister(pinfo->tzd[i]); | ||
512 | configure_adc(0); | ||
513 | kfree(pinfo); | ||
514 | return ret; | ||
515 | } | ||
516 | |||
517 | /** | ||
518 | * mid_thermal_remove - mfld thermal finalize | ||
519 | * @dev: platform device structure | ||
520 | * | ||
521 | * MLFD thermal remove unregisters all the sensors from the generic | ||
522 | * thermal framework. Can sleep. | ||
523 | */ | ||
524 | static int mid_thermal_remove(struct platform_device *pdev) | ||
525 | { | ||
526 | int i; | ||
527 | struct platform_info *pinfo = platform_get_drvdata(pdev); | ||
528 | |||
529 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) | ||
530 | thermal_zone_device_unregister(pinfo->tzd[i]); | ||
531 | |||
532 | platform_set_drvdata(pdev, NULL); | ||
533 | |||
534 | /* Stop the ADC */ | ||
535 | return configure_adc(0); | ||
536 | } | ||
537 | |||
538 | /********************************************************************* | ||
539 | * Driver initialisation and finalization | ||
540 | *********************************************************************/ | ||
541 | |||
542 | #define DRIVER_NAME "msic_sensor" | ||
543 | |||
544 | static const struct platform_device_id therm_id_table[] = { | ||
545 | { DRIVER_NAME, 1 }, | ||
546 | { } | ||
547 | }; | ||
548 | |||
549 | static struct platform_driver mid_thermal_driver = { | ||
550 | .driver = { | ||
551 | .name = DRIVER_NAME, | ||
552 | .owner = THIS_MODULE, | ||
553 | }, | ||
554 | .probe = mid_thermal_probe, | ||
555 | .suspend = mid_thermal_suspend, | ||
556 | .resume = mid_thermal_resume, | ||
557 | .remove = __devexit_p(mid_thermal_remove), | ||
558 | .id_table = therm_id_table, | ||
559 | }; | ||
560 | |||
561 | static int __init mid_thermal_module_init(void) | ||
562 | { | ||
563 | return platform_driver_register(&mid_thermal_driver); | ||
564 | } | ||
565 | |||
566 | static void __exit mid_thermal_module_exit(void) | ||
567 | { | ||
568 | platform_driver_unregister(&mid_thermal_driver); | ||
569 | } | ||
570 | |||
571 | module_init(mid_thermal_module_init); | ||
572 | module_exit(mid_thermal_module_exit); | ||
573 | |||
574 | MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); | ||
575 | MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); | ||
576 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c index 61433d492862..d653104b59cb 100644 --- a/drivers/platform/x86/intel_pmic_gpio.c +++ b/drivers/platform/x86/intel_pmic_gpio.c | |||
@@ -257,9 +257,11 @@ static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) | |||
257 | } | 257 | } |
258 | 258 | ||
259 | for (i = 0; i < 8; i++) { | 259 | for (i = 0; i < 8; i++) { |
260 | set_irq_chip_and_handler_name(i + pg->irq_base, &pmic_irqchip, | 260 | irq_set_chip_and_handler_name(i + pg->irq_base, |
261 | handle_simple_irq, "demux"); | 261 | &pmic_irqchip, |
262 | set_irq_chip_data(i + pg->irq_base, pg); | 262 | handle_simple_irq, |
263 | "demux"); | ||
264 | irq_set_chip_data(i + pg->irq_base, pg); | ||
263 | } | 265 | } |
264 | return 0; | 266 | return 0; |
265 | err: | 267 | err: |
diff --git a/drivers/platform/x86/intel_rar_register.c b/drivers/platform/x86/intel_rar_register.c index 2b11a33325e6..bde47e9080cd 100644 --- a/drivers/platform/x86/intel_rar_register.c +++ b/drivers/platform/x86/intel_rar_register.c | |||
@@ -485,7 +485,7 @@ EXPORT_SYMBOL(rar_lock); | |||
485 | * | 485 | * |
486 | * The register_rar function is to used by other device drivers | 486 | * The register_rar function is to used by other device drivers |
487 | * to ensure that this driver is ready. As we cannot be sure of | 487 | * to ensure that this driver is ready. As we cannot be sure of |
488 | * the compile/execute order of drivers in ther kernel, it is | 488 | * the compile/execute order of drivers in the kernel, it is |
489 | * best to give this driver a callback function to call when | 489 | * best to give this driver a callback function to call when |
490 | * it is ready to give out addresses. The callback function | 490 | * it is ready to give out addresses. The callback function |
491 | * would have those steps that continue the initialization of | 491 | * would have those steps that continue the initialization of |
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index a91d510a798b..940accbe28d3 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c | |||
@@ -9,7 +9,7 @@ | |||
9 | * as published by the Free Software Foundation; version 2 | 9 | * as published by the Free Software Foundation; version 2 |
10 | * of the License. | 10 | * of the License. |
11 | * | 11 | * |
12 | * SCU runing in ARC processor communicates with other entity running in IA | 12 | * SCU running in ARC processor communicates with other entity running in IA |
13 | * core through IPC mechanism which in turn messaging between IA core ad SCU. | 13 | * core through IPC mechanism which in turn messaging between IA core ad SCU. |
14 | * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and | 14 | * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and |
15 | * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with | 15 | * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with |
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 142d38579314..23fb2afda00b 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c | |||
@@ -51,6 +51,8 @@ | |||
51 | * laptop as MSI S270. YMMV. | 51 | * laptop as MSI S270. YMMV. |
52 | */ | 52 | */ |
53 | 53 | ||
54 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
55 | |||
54 | #include <linux/module.h> | 56 | #include <linux/module.h> |
55 | #include <linux/kernel.h> | 57 | #include <linux/kernel.h> |
56 | #include <linux/init.h> | 58 | #include <linux/init.h> |
@@ -60,6 +62,8 @@ | |||
60 | #include <linux/platform_device.h> | 62 | #include <linux/platform_device.h> |
61 | #include <linux/rfkill.h> | 63 | #include <linux/rfkill.h> |
62 | #include <linux/i8042.h> | 64 | #include <linux/i8042.h> |
65 | #include <linux/input.h> | ||
66 | #include <linux/input/sparse-keymap.h> | ||
63 | 67 | ||
64 | #define MSI_DRIVER_VERSION "0.5" | 68 | #define MSI_DRIVER_VERSION "0.5" |
65 | 69 | ||
@@ -78,6 +82,9 @@ | |||
78 | #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d | 82 | #define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d |
79 | #define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0) | 83 | #define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0) |
80 | 84 | ||
85 | #define MSI_STANDARD_EC_TOUCHPAD_ADDRESS 0xe4 | ||
86 | #define MSI_STANDARD_EC_TOUCHPAD_MASK (1 << 4) | ||
87 | |||
81 | static int msi_laptop_resume(struct platform_device *device); | 88 | static int msi_laptop_resume(struct platform_device *device); |
82 | 89 | ||
83 | #define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f | 90 | #define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f |
@@ -90,6 +97,14 @@ static int auto_brightness; | |||
90 | module_param(auto_brightness, int, 0); | 97 | module_param(auto_brightness, int, 0); |
91 | MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)"); | 98 | MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)"); |
92 | 99 | ||
100 | static const struct key_entry msi_laptop_keymap[] = { | ||
101 | {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, /* Touch Pad On */ | ||
102 | {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */ | ||
103 | {KE_END, 0} | ||
104 | }; | ||
105 | |||
106 | static struct input_dev *msi_laptop_input_dev; | ||
107 | |||
93 | static bool old_ec_model; | 108 | static bool old_ec_model; |
94 | static int wlan_s, bluetooth_s, threeg_s; | 109 | static int wlan_s, bluetooth_s, threeg_s; |
95 | static int threeg_exists; | 110 | static int threeg_exists; |
@@ -432,8 +447,7 @@ static struct platform_device *msipf_device; | |||
432 | 447 | ||
433 | static int dmi_check_cb(const struct dmi_system_id *id) | 448 | static int dmi_check_cb(const struct dmi_system_id *id) |
434 | { | 449 | { |
435 | printk(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n", | 450 | pr_info("Identified laptop model '%s'.\n", id->ident); |
436 | id->ident); | ||
437 | return 1; | 451 | return 1; |
438 | } | 452 | } |
439 | 453 | ||
@@ -605,6 +619,21 @@ static void msi_update_rfkill(struct work_struct *ignored) | |||
605 | } | 619 | } |
606 | static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill); | 620 | static DECLARE_DELAYED_WORK(msi_rfkill_work, msi_update_rfkill); |
607 | 621 | ||
622 | static void msi_send_touchpad_key(struct work_struct *ignored) | ||
623 | { | ||
624 | u8 rdata; | ||
625 | int result; | ||
626 | |||
627 | result = ec_read(MSI_STANDARD_EC_TOUCHPAD_ADDRESS, &rdata); | ||
628 | if (result < 0) | ||
629 | return; | ||
630 | |||
631 | sparse_keymap_report_event(msi_laptop_input_dev, | ||
632 | (rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ? | ||
633 | KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true); | ||
634 | } | ||
635 | static DECLARE_DELAYED_WORK(msi_touchpad_work, msi_send_touchpad_key); | ||
636 | |||
608 | static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, | 637 | static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, |
609 | struct serio *port) | 638 | struct serio *port) |
610 | { | 639 | { |
@@ -613,12 +642,17 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, | |||
613 | if (str & 0x20) | 642 | if (str & 0x20) |
614 | return false; | 643 | return false; |
615 | 644 | ||
616 | /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan*/ | 645 | /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/ |
617 | if (unlikely(data == 0xe0)) { | 646 | if (unlikely(data == 0xe0)) { |
618 | extended = true; | 647 | extended = true; |
619 | return false; | 648 | return false; |
620 | } else if (unlikely(extended)) { | 649 | } else if (unlikely(extended)) { |
650 | extended = false; | ||
621 | switch (data) { | 651 | switch (data) { |
652 | case 0xE4: | ||
653 | schedule_delayed_work(&msi_touchpad_work, | ||
654 | round_jiffies_relative(0.5 * HZ)); | ||
655 | break; | ||
622 | case 0x54: | 656 | case 0x54: |
623 | case 0x62: | 657 | case 0x62: |
624 | case 0x76: | 658 | case 0x76: |
@@ -626,7 +660,6 @@ static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str, | |||
626 | round_jiffies_relative(0.5 * HZ)); | 660 | round_jiffies_relative(0.5 * HZ)); |
627 | break; | 661 | break; |
628 | } | 662 | } |
629 | extended = false; | ||
630 | } | 663 | } |
631 | 664 | ||
632 | return false; | 665 | return false; |
@@ -731,6 +764,42 @@ static int msi_laptop_resume(struct platform_device *device) | |||
731 | return 0; | 764 | return 0; |
732 | } | 765 | } |
733 | 766 | ||
767 | static int __init msi_laptop_input_setup(void) | ||
768 | { | ||
769 | int err; | ||
770 | |||
771 | msi_laptop_input_dev = input_allocate_device(); | ||
772 | if (!msi_laptop_input_dev) | ||
773 | return -ENOMEM; | ||
774 | |||
775 | msi_laptop_input_dev->name = "MSI Laptop hotkeys"; | ||
776 | msi_laptop_input_dev->phys = "msi-laptop/input0"; | ||
777 | msi_laptop_input_dev->id.bustype = BUS_HOST; | ||
778 | |||
779 | err = sparse_keymap_setup(msi_laptop_input_dev, | ||
780 | msi_laptop_keymap, NULL); | ||
781 | if (err) | ||
782 | goto err_free_dev; | ||
783 | |||
784 | err = input_register_device(msi_laptop_input_dev); | ||
785 | if (err) | ||
786 | goto err_free_keymap; | ||
787 | |||
788 | return 0; | ||
789 | |||
790 | err_free_keymap: | ||
791 | sparse_keymap_free(msi_laptop_input_dev); | ||
792 | err_free_dev: | ||
793 | input_free_device(msi_laptop_input_dev); | ||
794 | return err; | ||
795 | } | ||
796 | |||
797 | static void msi_laptop_input_destroy(void) | ||
798 | { | ||
799 | sparse_keymap_free(msi_laptop_input_dev); | ||
800 | input_unregister_device(msi_laptop_input_dev); | ||
801 | } | ||
802 | |||
734 | static int load_scm_model_init(struct platform_device *sdev) | 803 | static int load_scm_model_init(struct platform_device *sdev) |
735 | { | 804 | { |
736 | u8 data; | 805 | u8 data; |
@@ -759,16 +828,23 @@ static int load_scm_model_init(struct platform_device *sdev) | |||
759 | if (result < 0) | 828 | if (result < 0) |
760 | goto fail_rfkill; | 829 | goto fail_rfkill; |
761 | 830 | ||
831 | /* setup input device */ | ||
832 | result = msi_laptop_input_setup(); | ||
833 | if (result) | ||
834 | goto fail_input; | ||
835 | |||
762 | result = i8042_install_filter(msi_laptop_i8042_filter); | 836 | result = i8042_install_filter(msi_laptop_i8042_filter); |
763 | if (result) { | 837 | if (result) { |
764 | printk(KERN_ERR | 838 | pr_err("Unable to install key filter\n"); |
765 | "msi-laptop: Unable to install key filter\n"); | ||
766 | goto fail_filter; | 839 | goto fail_filter; |
767 | } | 840 | } |
768 | 841 | ||
769 | return 0; | 842 | return 0; |
770 | 843 | ||
771 | fail_filter: | 844 | fail_filter: |
845 | msi_laptop_input_destroy(); | ||
846 | |||
847 | fail_input: | ||
772 | rfkill_cleanup(); | 848 | rfkill_cleanup(); |
773 | 849 | ||
774 | fail_rfkill: | 850 | fail_rfkill: |
@@ -799,7 +875,7 @@ static int __init msi_init(void) | |||
799 | /* Register backlight stuff */ | 875 | /* Register backlight stuff */ |
800 | 876 | ||
801 | if (acpi_video_backlight_support()) { | 877 | if (acpi_video_backlight_support()) { |
802 | printk(KERN_INFO "MSI: Brightness ignored, must be controlled " | 878 | pr_info("Brightness ignored, must be controlled " |
803 | "by ACPI video driver\n"); | 879 | "by ACPI video driver\n"); |
804 | } else { | 880 | } else { |
805 | struct backlight_properties props; | 881 | struct backlight_properties props; |
@@ -854,7 +930,7 @@ static int __init msi_init(void) | |||
854 | if (auto_brightness != 2) | 930 | if (auto_brightness != 2) |
855 | set_auto_brightness(auto_brightness); | 931 | set_auto_brightness(auto_brightness); |
856 | 932 | ||
857 | printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n"); | 933 | pr_info("driver "MSI_DRIVER_VERSION" successfully loaded.\n"); |
858 | 934 | ||
859 | return 0; | 935 | return 0; |
860 | 936 | ||
@@ -886,6 +962,7 @@ static void __exit msi_cleanup(void) | |||
886 | { | 962 | { |
887 | if (load_scm_model) { | 963 | if (load_scm_model) { |
888 | i8042_remove_filter(msi_laptop_i8042_filter); | 964 | i8042_remove_filter(msi_laptop_i8042_filter); |
965 | msi_laptop_input_destroy(); | ||
889 | cancel_delayed_work_sync(&msi_rfkill_work); | 966 | cancel_delayed_work_sync(&msi_rfkill_work); |
890 | rfkill_cleanup(); | 967 | rfkill_cleanup(); |
891 | } | 968 | } |
@@ -901,7 +978,7 @@ static void __exit msi_cleanup(void) | |||
901 | if (auto_brightness != 2) | 978 | if (auto_brightness != 2) |
902 | set_auto_brightness(1); | 979 | set_auto_brightness(1); |
903 | 980 | ||
904 | printk(KERN_INFO "msi-laptop: driver unloaded.\n"); | 981 | pr_info("driver unloaded.\n"); |
905 | } | 982 | } |
906 | 983 | ||
907 | module_init(msi_init); | 984 | module_init(msi_init); |
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c new file mode 100644 index 000000000000..de434c6dc2d6 --- /dev/null +++ b/drivers/platform/x86/samsung-laptop.c | |||
@@ -0,0 +1,832 @@ | |||
1 | /* | ||
2 | * Samsung Laptop driver | ||
3 | * | ||
4 | * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) | ||
5 | * Copyright (C) 2009,2011 Novell Inc. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License version 2 as published by | ||
9 | * the Free Software Foundation. | ||
10 | * | ||
11 | */ | ||
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
13 | |||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/delay.h> | ||
18 | #include <linux/pci.h> | ||
19 | #include <linux/backlight.h> | ||
20 | #include <linux/fb.h> | ||
21 | #include <linux/dmi.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/rfkill.h> | ||
24 | |||
25 | /* | ||
26 | * This driver is needed because a number of Samsung laptops do not hook | ||
27 | * their control settings through ACPI. So we have to poke around in the | ||
28 | * BIOS to do things like brightness values, and "special" key controls. | ||
29 | */ | ||
30 | |||
31 | /* | ||
32 | * We have 0 - 8 as valid brightness levels. The specs say that level 0 should | ||
33 | * be reserved by the BIOS (which really doesn't make much sense), we tell | ||
34 | * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8 | ||
35 | */ | ||
36 | #define MAX_BRIGHT 0x07 | ||
37 | |||
38 | |||
39 | #define SABI_IFACE_MAIN 0x00 | ||
40 | #define SABI_IFACE_SUB 0x02 | ||
41 | #define SABI_IFACE_COMPLETE 0x04 | ||
42 | #define SABI_IFACE_DATA 0x05 | ||
43 | |||
44 | /* Structure to get data back to the calling function */ | ||
45 | struct sabi_retval { | ||
46 | u8 retval[20]; | ||
47 | }; | ||
48 | |||
49 | struct sabi_header_offsets { | ||
50 | u8 port; | ||
51 | u8 re_mem; | ||
52 | u8 iface_func; | ||
53 | u8 en_mem; | ||
54 | u8 data_offset; | ||
55 | u8 data_segment; | ||
56 | }; | ||
57 | |||
58 | struct sabi_commands { | ||
59 | /* | ||
60 | * Brightness is 0 - 8, as described above. | ||
61 | * Value 0 is for the BIOS to use | ||
62 | */ | ||
63 | u8 get_brightness; | ||
64 | u8 set_brightness; | ||
65 | |||
66 | /* | ||
67 | * first byte: | ||
68 | * 0x00 - wireless is off | ||
69 | * 0x01 - wireless is on | ||
70 | * second byte: | ||
71 | * 0x02 - 3G is off | ||
72 | * 0x03 - 3G is on | ||
73 | * TODO, verify 3G is correct, that doesn't seem right... | ||
74 | */ | ||
75 | u8 get_wireless_button; | ||
76 | u8 set_wireless_button; | ||
77 | |||
78 | /* 0 is off, 1 is on */ | ||
79 | u8 get_backlight; | ||
80 | u8 set_backlight; | ||
81 | |||
82 | /* | ||
83 | * 0x80 or 0x00 - no action | ||
84 | * 0x81 - recovery key pressed | ||
85 | */ | ||
86 | u8 get_recovery_mode; | ||
87 | u8 set_recovery_mode; | ||
88 | |||
89 | /* | ||
90 | * on seclinux: 0 is low, 1 is high, | ||
91 | * on swsmi: 0 is normal, 1 is silent, 2 is turbo | ||
92 | */ | ||
93 | u8 get_performance_level; | ||
94 | u8 set_performance_level; | ||
95 | |||
96 | /* | ||
97 | * Tell the BIOS that Linux is running on this machine. | ||
98 | * 81 is on, 80 is off | ||
99 | */ | ||
100 | u8 set_linux; | ||
101 | }; | ||
102 | |||
103 | struct sabi_performance_level { | ||
104 | const char *name; | ||
105 | u8 value; | ||
106 | }; | ||
107 | |||
108 | struct sabi_config { | ||
109 | const char *test_string; | ||
110 | u16 main_function; | ||
111 | const struct sabi_header_offsets header_offsets; | ||
112 | const struct sabi_commands commands; | ||
113 | const struct sabi_performance_level performance_levels[4]; | ||
114 | u8 min_brightness; | ||
115 | u8 max_brightness; | ||
116 | }; | ||
117 | |||
118 | static const struct sabi_config sabi_configs[] = { | ||
119 | { | ||
120 | .test_string = "SECLINUX", | ||
121 | |||
122 | .main_function = 0x4c49, | ||
123 | |||
124 | .header_offsets = { | ||
125 | .port = 0x00, | ||
126 | .re_mem = 0x02, | ||
127 | .iface_func = 0x03, | ||
128 | .en_mem = 0x04, | ||
129 | .data_offset = 0x05, | ||
130 | .data_segment = 0x07, | ||
131 | }, | ||
132 | |||
133 | .commands = { | ||
134 | .get_brightness = 0x00, | ||
135 | .set_brightness = 0x01, | ||
136 | |||
137 | .get_wireless_button = 0x02, | ||
138 | .set_wireless_button = 0x03, | ||
139 | |||
140 | .get_backlight = 0x04, | ||
141 | .set_backlight = 0x05, | ||
142 | |||
143 | .get_recovery_mode = 0x06, | ||
144 | .set_recovery_mode = 0x07, | ||
145 | |||
146 | .get_performance_level = 0x08, | ||
147 | .set_performance_level = 0x09, | ||
148 | |||
149 | .set_linux = 0x0a, | ||
150 | }, | ||
151 | |||
152 | .performance_levels = { | ||
153 | { | ||
154 | .name = "silent", | ||
155 | .value = 0, | ||
156 | }, | ||
157 | { | ||
158 | .name = "normal", | ||
159 | .value = 1, | ||
160 | }, | ||
161 | { }, | ||
162 | }, | ||
163 | .min_brightness = 1, | ||
164 | .max_brightness = 8, | ||
165 | }, | ||
166 | { | ||
167 | .test_string = "SwSmi@", | ||
168 | |||
169 | .main_function = 0x5843, | ||
170 | |||
171 | .header_offsets = { | ||
172 | .port = 0x00, | ||
173 | .re_mem = 0x04, | ||
174 | .iface_func = 0x02, | ||
175 | .en_mem = 0x03, | ||
176 | .data_offset = 0x05, | ||
177 | .data_segment = 0x07, | ||
178 | }, | ||
179 | |||
180 | .commands = { | ||
181 | .get_brightness = 0x10, | ||
182 | .set_brightness = 0x11, | ||
183 | |||
184 | .get_wireless_button = 0x12, | ||
185 | .set_wireless_button = 0x13, | ||
186 | |||
187 | .get_backlight = 0x2d, | ||
188 | .set_backlight = 0x2e, | ||
189 | |||
190 | .get_recovery_mode = 0xff, | ||
191 | .set_recovery_mode = 0xff, | ||
192 | |||
193 | .get_performance_level = 0x31, | ||
194 | .set_performance_level = 0x32, | ||
195 | |||
196 | .set_linux = 0xff, | ||
197 | }, | ||
198 | |||
199 | .performance_levels = { | ||
200 | { | ||
201 | .name = "normal", | ||
202 | .value = 0, | ||
203 | }, | ||
204 | { | ||
205 | .name = "silent", | ||
206 | .value = 1, | ||
207 | }, | ||
208 | { | ||
209 | .name = "overclock", | ||
210 | .value = 2, | ||
211 | }, | ||
212 | { }, | ||
213 | }, | ||
214 | .min_brightness = 0, | ||
215 | .max_brightness = 8, | ||
216 | }, | ||
217 | { }, | ||
218 | }; | ||
219 | |||
220 | static const struct sabi_config *sabi_config; | ||
221 | |||
222 | static void __iomem *sabi; | ||
223 | static void __iomem *sabi_iface; | ||
224 | static void __iomem *f0000_segment; | ||
225 | static struct backlight_device *backlight_device; | ||
226 | static struct mutex sabi_mutex; | ||
227 | static struct platform_device *sdev; | ||
228 | static struct rfkill *rfk; | ||
229 | |||
230 | static int force; | ||
231 | module_param(force, bool, 0); | ||
232 | MODULE_PARM_DESC(force, | ||
233 | "Disable the DMI check and forces the driver to be loaded"); | ||
234 | |||
235 | static int debug; | ||
236 | module_param(debug, bool, S_IRUGO | S_IWUSR); | ||
237 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | ||
238 | |||
239 | static int sabi_get_command(u8 command, struct sabi_retval *sretval) | ||
240 | { | ||
241 | int retval = 0; | ||
242 | u16 port = readw(sabi + sabi_config->header_offsets.port); | ||
243 | u8 complete, iface_data; | ||
244 | |||
245 | mutex_lock(&sabi_mutex); | ||
246 | |||
247 | /* enable memory to be able to write to it */ | ||
248 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | ||
249 | |||
250 | /* write out the command */ | ||
251 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | ||
252 | writew(command, sabi_iface + SABI_IFACE_SUB); | ||
253 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | ||
254 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | ||
255 | |||
256 | /* write protect memory to make it safe */ | ||
257 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | ||
258 | |||
259 | /* see if the command actually succeeded */ | ||
260 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | ||
261 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | ||
262 | if (complete != 0xaa || iface_data == 0xff) { | ||
263 | pr_warn("SABI get command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | ||
264 | command, complete, iface_data); | ||
265 | retval = -EINVAL; | ||
266 | goto exit; | ||
267 | } | ||
268 | /* | ||
269 | * Save off the data into a structure so the caller use it. | ||
270 | * Right now we only want the first 4 bytes, | ||
271 | * There are commands that need more, but not for the ones we | ||
272 | * currently care about. | ||
273 | */ | ||
274 | sretval->retval[0] = readb(sabi_iface + SABI_IFACE_DATA); | ||
275 | sretval->retval[1] = readb(sabi_iface + SABI_IFACE_DATA + 1); | ||
276 | sretval->retval[2] = readb(sabi_iface + SABI_IFACE_DATA + 2); | ||
277 | sretval->retval[3] = readb(sabi_iface + SABI_IFACE_DATA + 3); | ||
278 | |||
279 | exit: | ||
280 | mutex_unlock(&sabi_mutex); | ||
281 | return retval; | ||
282 | |||
283 | } | ||
284 | |||
285 | static int sabi_set_command(u8 command, u8 data) | ||
286 | { | ||
287 | int retval = 0; | ||
288 | u16 port = readw(sabi + sabi_config->header_offsets.port); | ||
289 | u8 complete, iface_data; | ||
290 | |||
291 | mutex_lock(&sabi_mutex); | ||
292 | |||
293 | /* enable memory to be able to write to it */ | ||
294 | outb(readb(sabi + sabi_config->header_offsets.en_mem), port); | ||
295 | |||
296 | /* write out the command */ | ||
297 | writew(sabi_config->main_function, sabi_iface + SABI_IFACE_MAIN); | ||
298 | writew(command, sabi_iface + SABI_IFACE_SUB); | ||
299 | writeb(0, sabi_iface + SABI_IFACE_COMPLETE); | ||
300 | writeb(data, sabi_iface + SABI_IFACE_DATA); | ||
301 | outb(readb(sabi + sabi_config->header_offsets.iface_func), port); | ||
302 | |||
303 | /* write protect memory to make it safe */ | ||
304 | outb(readb(sabi + sabi_config->header_offsets.re_mem), port); | ||
305 | |||
306 | /* see if the command actually succeeded */ | ||
307 | complete = readb(sabi_iface + SABI_IFACE_COMPLETE); | ||
308 | iface_data = readb(sabi_iface + SABI_IFACE_DATA); | ||
309 | if (complete != 0xaa || iface_data == 0xff) { | ||
310 | pr_warn("SABI set command 0x%02x failed with completion flag 0x%02x and data 0x%02x\n", | ||
311 | command, complete, iface_data); | ||
312 | retval = -EINVAL; | ||
313 | } | ||
314 | |||
315 | mutex_unlock(&sabi_mutex); | ||
316 | return retval; | ||
317 | } | ||
318 | |||
319 | static void test_backlight(void) | ||
320 | { | ||
321 | struct sabi_retval sretval; | ||
322 | |||
323 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | ||
324 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | ||
325 | |||
326 | sabi_set_command(sabi_config->commands.set_backlight, 0); | ||
327 | printk(KERN_DEBUG "backlight should be off\n"); | ||
328 | |||
329 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | ||
330 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | ||
331 | |||
332 | msleep(1000); | ||
333 | |||
334 | sabi_set_command(sabi_config->commands.set_backlight, 1); | ||
335 | printk(KERN_DEBUG "backlight should be on\n"); | ||
336 | |||
337 | sabi_get_command(sabi_config->commands.get_backlight, &sretval); | ||
338 | printk(KERN_DEBUG "backlight = 0x%02x\n", sretval.retval[0]); | ||
339 | } | ||
340 | |||
341 | static void test_wireless(void) | ||
342 | { | ||
343 | struct sabi_retval sretval; | ||
344 | |||
345 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
346 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
347 | |||
348 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | ||
349 | printk(KERN_DEBUG "wireless led should be off\n"); | ||
350 | |||
351 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
352 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
353 | |||
354 | msleep(1000); | ||
355 | |||
356 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | ||
357 | printk(KERN_DEBUG "wireless led should be on\n"); | ||
358 | |||
359 | sabi_get_command(sabi_config->commands.get_wireless_button, &sretval); | ||
360 | printk(KERN_DEBUG "wireless led = 0x%02x\n", sretval.retval[0]); | ||
361 | } | ||
362 | |||
363 | static u8 read_brightness(void) | ||
364 | { | ||
365 | struct sabi_retval sretval; | ||
366 | int user_brightness = 0; | ||
367 | int retval; | ||
368 | |||
369 | retval = sabi_get_command(sabi_config->commands.get_brightness, | ||
370 | &sretval); | ||
371 | if (!retval) { | ||
372 | user_brightness = sretval.retval[0]; | ||
373 | if (user_brightness != 0) | ||
374 | user_brightness -= sabi_config->min_brightness; | ||
375 | } | ||
376 | return user_brightness; | ||
377 | } | ||
378 | |||
379 | static void set_brightness(u8 user_brightness) | ||
380 | { | ||
381 | u8 user_level = user_brightness - sabi_config->min_brightness; | ||
382 | |||
383 | sabi_set_command(sabi_config->commands.set_brightness, user_level); | ||
384 | } | ||
385 | |||
386 | static int get_brightness(struct backlight_device *bd) | ||
387 | { | ||
388 | return (int)read_brightness(); | ||
389 | } | ||
390 | |||
391 | static int update_status(struct backlight_device *bd) | ||
392 | { | ||
393 | set_brightness(bd->props.brightness); | ||
394 | |||
395 | if (bd->props.power == FB_BLANK_UNBLANK) | ||
396 | sabi_set_command(sabi_config->commands.set_backlight, 1); | ||
397 | else | ||
398 | sabi_set_command(sabi_config->commands.set_backlight, 0); | ||
399 | return 0; | ||
400 | } | ||
401 | |||
402 | static const struct backlight_ops backlight_ops = { | ||
403 | .get_brightness = get_brightness, | ||
404 | .update_status = update_status, | ||
405 | }; | ||
406 | |||
407 | static int rfkill_set(void *data, bool blocked) | ||
408 | { | ||
409 | /* Do something with blocked...*/ | ||
410 | /* | ||
411 | * blocked == false is on | ||
412 | * blocked == true is off | ||
413 | */ | ||
414 | if (blocked) | ||
415 | sabi_set_command(sabi_config->commands.set_wireless_button, 0); | ||
416 | else | ||
417 | sabi_set_command(sabi_config->commands.set_wireless_button, 1); | ||
418 | |||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | static struct rfkill_ops rfkill_ops = { | ||
423 | .set_block = rfkill_set, | ||
424 | }; | ||
425 | |||
426 | static int init_wireless(struct platform_device *sdev) | ||
427 | { | ||
428 | int retval; | ||
429 | |||
430 | rfk = rfkill_alloc("samsung-wifi", &sdev->dev, RFKILL_TYPE_WLAN, | ||
431 | &rfkill_ops, NULL); | ||
432 | if (!rfk) | ||
433 | return -ENOMEM; | ||
434 | |||
435 | retval = rfkill_register(rfk); | ||
436 | if (retval) { | ||
437 | rfkill_destroy(rfk); | ||
438 | return -ENODEV; | ||
439 | } | ||
440 | |||
441 | return 0; | ||
442 | } | ||
443 | |||
444 | static void destroy_wireless(void) | ||
445 | { | ||
446 | rfkill_unregister(rfk); | ||
447 | rfkill_destroy(rfk); | ||
448 | } | ||
449 | |||
450 | static ssize_t get_performance_level(struct device *dev, | ||
451 | struct device_attribute *attr, char *buf) | ||
452 | { | ||
453 | struct sabi_retval sretval; | ||
454 | int retval; | ||
455 | int i; | ||
456 | |||
457 | /* Read the state */ | ||
458 | retval = sabi_get_command(sabi_config->commands.get_performance_level, | ||
459 | &sretval); | ||
460 | if (retval) | ||
461 | return retval; | ||
462 | |||
463 | /* The logic is backwards, yeah, lots of fun... */ | ||
464 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | ||
465 | if (sretval.retval[0] == sabi_config->performance_levels[i].value) | ||
466 | return sprintf(buf, "%s\n", sabi_config->performance_levels[i].name); | ||
467 | } | ||
468 | return sprintf(buf, "%s\n", "unknown"); | ||
469 | } | ||
470 | |||
471 | static ssize_t set_performance_level(struct device *dev, | ||
472 | struct device_attribute *attr, const char *buf, | ||
473 | size_t count) | ||
474 | { | ||
475 | if (count >= 1) { | ||
476 | int i; | ||
477 | for (i = 0; sabi_config->performance_levels[i].name; ++i) { | ||
478 | const struct sabi_performance_level *level = | ||
479 | &sabi_config->performance_levels[i]; | ||
480 | if (!strncasecmp(level->name, buf, strlen(level->name))) { | ||
481 | sabi_set_command(sabi_config->commands.set_performance_level, | ||
482 | level->value); | ||
483 | break; | ||
484 | } | ||
485 | } | ||
486 | if (!sabi_config->performance_levels[i].name) | ||
487 | return -EINVAL; | ||
488 | } | ||
489 | return count; | ||
490 | } | ||
491 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | ||
492 | get_performance_level, set_performance_level); | ||
493 | |||
494 | |||
495 | static int __init dmi_check_cb(const struct dmi_system_id *id) | ||
496 | { | ||
497 | pr_info("found laptop model '%s'\n", | ||
498 | id->ident); | ||
499 | return 1; | ||
500 | } | ||
501 | |||
502 | static struct dmi_system_id __initdata samsung_dmi_table[] = { | ||
503 | { | ||
504 | .ident = "N128", | ||
505 | .matches = { | ||
506 | DMI_MATCH(DMI_SYS_VENDOR, | ||
507 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
508 | DMI_MATCH(DMI_PRODUCT_NAME, "N128"), | ||
509 | DMI_MATCH(DMI_BOARD_NAME, "N128"), | ||
510 | }, | ||
511 | .callback = dmi_check_cb, | ||
512 | }, | ||
513 | { | ||
514 | .ident = "N130", | ||
515 | .matches = { | ||
516 | DMI_MATCH(DMI_SYS_VENDOR, | ||
517 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
518 | DMI_MATCH(DMI_PRODUCT_NAME, "N130"), | ||
519 | DMI_MATCH(DMI_BOARD_NAME, "N130"), | ||
520 | }, | ||
521 | .callback = dmi_check_cb, | ||
522 | }, | ||
523 | { | ||
524 | .ident = "X125", | ||
525 | .matches = { | ||
526 | DMI_MATCH(DMI_SYS_VENDOR, | ||
527 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
528 | DMI_MATCH(DMI_PRODUCT_NAME, "X125"), | ||
529 | DMI_MATCH(DMI_BOARD_NAME, "X125"), | ||
530 | }, | ||
531 | .callback = dmi_check_cb, | ||
532 | }, | ||
533 | { | ||
534 | .ident = "X120/X170", | ||
535 | .matches = { | ||
536 | DMI_MATCH(DMI_SYS_VENDOR, | ||
537 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
538 | DMI_MATCH(DMI_PRODUCT_NAME, "X120/X170"), | ||
539 | DMI_MATCH(DMI_BOARD_NAME, "X120/X170"), | ||
540 | }, | ||
541 | .callback = dmi_check_cb, | ||
542 | }, | ||
543 | { | ||
544 | .ident = "NC10", | ||
545 | .matches = { | ||
546 | DMI_MATCH(DMI_SYS_VENDOR, | ||
547 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
548 | DMI_MATCH(DMI_PRODUCT_NAME, "NC10"), | ||
549 | DMI_MATCH(DMI_BOARD_NAME, "NC10"), | ||
550 | }, | ||
551 | .callback = dmi_check_cb, | ||
552 | }, | ||
553 | { | ||
554 | .ident = "NP-Q45", | ||
555 | .matches = { | ||
556 | DMI_MATCH(DMI_SYS_VENDOR, | ||
557 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
558 | DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"), | ||
559 | DMI_MATCH(DMI_BOARD_NAME, "SQ45S70S"), | ||
560 | }, | ||
561 | .callback = dmi_check_cb, | ||
562 | }, | ||
563 | { | ||
564 | .ident = "X360", | ||
565 | .matches = { | ||
566 | DMI_MATCH(DMI_SYS_VENDOR, | ||
567 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
568 | DMI_MATCH(DMI_PRODUCT_NAME, "X360"), | ||
569 | DMI_MATCH(DMI_BOARD_NAME, "X360"), | ||
570 | }, | ||
571 | .callback = dmi_check_cb, | ||
572 | }, | ||
573 | { | ||
574 | .ident = "R518", | ||
575 | .matches = { | ||
576 | DMI_MATCH(DMI_SYS_VENDOR, | ||
577 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
578 | DMI_MATCH(DMI_PRODUCT_NAME, "R518"), | ||
579 | DMI_MATCH(DMI_BOARD_NAME, "R518"), | ||
580 | }, | ||
581 | .callback = dmi_check_cb, | ||
582 | }, | ||
583 | { | ||
584 | .ident = "R519/R719", | ||
585 | .matches = { | ||
586 | DMI_MATCH(DMI_SYS_VENDOR, | ||
587 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
588 | DMI_MATCH(DMI_PRODUCT_NAME, "R519/R719"), | ||
589 | DMI_MATCH(DMI_BOARD_NAME, "R519/R719"), | ||
590 | }, | ||
591 | .callback = dmi_check_cb, | ||
592 | }, | ||
593 | { | ||
594 | .ident = "N150/N210/N220", | ||
595 | .matches = { | ||
596 | DMI_MATCH(DMI_SYS_VENDOR, | ||
597 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
598 | DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"), | ||
599 | DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"), | ||
600 | }, | ||
601 | .callback = dmi_check_cb, | ||
602 | }, | ||
603 | { | ||
604 | .ident = "N150P/N210P/N220P", | ||
605 | .matches = { | ||
606 | DMI_MATCH(DMI_SYS_VENDOR, | ||
607 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
608 | DMI_MATCH(DMI_PRODUCT_NAME, "N150P/N210P/N220P"), | ||
609 | DMI_MATCH(DMI_BOARD_NAME, "N150P/N210P/N220P"), | ||
610 | }, | ||
611 | .callback = dmi_check_cb, | ||
612 | }, | ||
613 | { | ||
614 | .ident = "R530/R730", | ||
615 | .matches = { | ||
616 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
617 | DMI_MATCH(DMI_PRODUCT_NAME, "R530/R730"), | ||
618 | DMI_MATCH(DMI_BOARD_NAME, "R530/R730"), | ||
619 | }, | ||
620 | .callback = dmi_check_cb, | ||
621 | }, | ||
622 | { | ||
623 | .ident = "NF110/NF210/NF310", | ||
624 | .matches = { | ||
625 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
626 | DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"), | ||
627 | DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"), | ||
628 | }, | ||
629 | .callback = dmi_check_cb, | ||
630 | }, | ||
631 | { | ||
632 | .ident = "N145P/N250P/N260P", | ||
633 | .matches = { | ||
634 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
635 | DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"), | ||
636 | DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"), | ||
637 | }, | ||
638 | .callback = dmi_check_cb, | ||
639 | }, | ||
640 | { | ||
641 | .ident = "R70/R71", | ||
642 | .matches = { | ||
643 | DMI_MATCH(DMI_SYS_VENDOR, | ||
644 | "SAMSUNG ELECTRONICS CO., LTD."), | ||
645 | DMI_MATCH(DMI_PRODUCT_NAME, "R70/R71"), | ||
646 | DMI_MATCH(DMI_BOARD_NAME, "R70/R71"), | ||
647 | }, | ||
648 | .callback = dmi_check_cb, | ||
649 | }, | ||
650 | { | ||
651 | .ident = "P460", | ||
652 | .matches = { | ||
653 | DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), | ||
654 | DMI_MATCH(DMI_PRODUCT_NAME, "P460"), | ||
655 | DMI_MATCH(DMI_BOARD_NAME, "P460"), | ||
656 | }, | ||
657 | .callback = dmi_check_cb, | ||
658 | }, | ||
659 | { }, | ||
660 | }; | ||
661 | MODULE_DEVICE_TABLE(dmi, samsung_dmi_table); | ||
662 | |||
663 | static int find_signature(void __iomem *memcheck, const char *testStr) | ||
664 | { | ||
665 | int i = 0; | ||
666 | int loca; | ||
667 | |||
668 | for (loca = 0; loca < 0xffff; loca++) { | ||
669 | char temp = readb(memcheck + loca); | ||
670 | |||
671 | if (temp == testStr[i]) { | ||
672 | if (i == strlen(testStr)-1) | ||
673 | break; | ||
674 | ++i; | ||
675 | } else { | ||
676 | i = 0; | ||
677 | } | ||
678 | } | ||
679 | return loca; | ||
680 | } | ||
681 | |||
682 | static int __init samsung_init(void) | ||
683 | { | ||
684 | struct backlight_properties props; | ||
685 | struct sabi_retval sretval; | ||
686 | unsigned int ifaceP; | ||
687 | int i; | ||
688 | int loca; | ||
689 | int retval; | ||
690 | |||
691 | mutex_init(&sabi_mutex); | ||
692 | |||
693 | if (!force && !dmi_check_system(samsung_dmi_table)) | ||
694 | return -ENODEV; | ||
695 | |||
696 | f0000_segment = ioremap_nocache(0xf0000, 0xffff); | ||
697 | if (!f0000_segment) { | ||
698 | pr_err("Can't map the segment at 0xf0000\n"); | ||
699 | return -EINVAL; | ||
700 | } | ||
701 | |||
702 | /* Try to find one of the signatures in memory to find the header */ | ||
703 | for (i = 0; sabi_configs[i].test_string != 0; ++i) { | ||
704 | sabi_config = &sabi_configs[i]; | ||
705 | loca = find_signature(f0000_segment, sabi_config->test_string); | ||
706 | if (loca != 0xffff) | ||
707 | break; | ||
708 | } | ||
709 | |||
710 | if (loca == 0xffff) { | ||
711 | pr_err("This computer does not support SABI\n"); | ||
712 | goto error_no_signature; | ||
713 | } | ||
714 | |||
715 | /* point to the SMI port Number */ | ||
716 | loca += 1; | ||
717 | sabi = (f0000_segment + loca); | ||
718 | |||
719 | if (debug) { | ||
720 | printk(KERN_DEBUG "This computer supports SABI==%x\n", | ||
721 | loca + 0xf0000 - 6); | ||
722 | printk(KERN_DEBUG "SABI header:\n"); | ||
723 | printk(KERN_DEBUG " SMI Port Number = 0x%04x\n", | ||
724 | readw(sabi + sabi_config->header_offsets.port)); | ||
725 | printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n", | ||
726 | readb(sabi + sabi_config->header_offsets.iface_func)); | ||
727 | printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n", | ||
728 | readb(sabi + sabi_config->header_offsets.en_mem)); | ||
729 | printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n", | ||
730 | readb(sabi + sabi_config->header_offsets.re_mem)); | ||
731 | printk(KERN_DEBUG " SABI data offset = 0x%04x\n", | ||
732 | readw(sabi + sabi_config->header_offsets.data_offset)); | ||
733 | printk(KERN_DEBUG " SABI data segment = 0x%04x\n", | ||
734 | readw(sabi + sabi_config->header_offsets.data_segment)); | ||
735 | } | ||
736 | |||
737 | /* Get a pointer to the SABI Interface */ | ||
738 | ifaceP = (readw(sabi + sabi_config->header_offsets.data_segment) & 0x0ffff) << 4; | ||
739 | ifaceP += readw(sabi + sabi_config->header_offsets.data_offset) & 0x0ffff; | ||
740 | sabi_iface = ioremap_nocache(ifaceP, 16); | ||
741 | if (!sabi_iface) { | ||
742 | pr_err("Can't remap %x\n", ifaceP); | ||
743 | goto exit; | ||
744 | } | ||
745 | if (debug) { | ||
746 | printk(KERN_DEBUG "ifaceP = 0x%08x\n", ifaceP); | ||
747 | printk(KERN_DEBUG "sabi_iface = %p\n", sabi_iface); | ||
748 | |||
749 | test_backlight(); | ||
750 | test_wireless(); | ||
751 | |||
752 | retval = sabi_get_command(sabi_config->commands.get_brightness, | ||
753 | &sretval); | ||
754 | printk(KERN_DEBUG "brightness = 0x%02x\n", sretval.retval[0]); | ||
755 | } | ||
756 | |||
757 | /* Turn on "Linux" mode in the BIOS */ | ||
758 | if (sabi_config->commands.set_linux != 0xff) { | ||
759 | retval = sabi_set_command(sabi_config->commands.set_linux, | ||
760 | 0x81); | ||
761 | if (retval) { | ||
762 | pr_warn("Linux mode was not set!\n"); | ||
763 | goto error_no_platform; | ||
764 | } | ||
765 | } | ||
766 | |||
767 | /* knock up a platform device to hang stuff off of */ | ||
768 | sdev = platform_device_register_simple("samsung", -1, NULL, 0); | ||
769 | if (IS_ERR(sdev)) | ||
770 | goto error_no_platform; | ||
771 | |||
772 | /* create a backlight device to talk to this one */ | ||
773 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
774 | props.max_brightness = sabi_config->max_brightness; | ||
775 | backlight_device = backlight_device_register("samsung", &sdev->dev, | ||
776 | NULL, &backlight_ops, | ||
777 | &props); | ||
778 | if (IS_ERR(backlight_device)) | ||
779 | goto error_no_backlight; | ||
780 | |||
781 | backlight_device->props.brightness = read_brightness(); | ||
782 | backlight_device->props.power = FB_BLANK_UNBLANK; | ||
783 | backlight_update_status(backlight_device); | ||
784 | |||
785 | retval = init_wireless(sdev); | ||
786 | if (retval) | ||
787 | goto error_no_rfk; | ||
788 | |||
789 | retval = device_create_file(&sdev->dev, &dev_attr_performance_level); | ||
790 | if (retval) | ||
791 | goto error_file_create; | ||
792 | |||
793 | exit: | ||
794 | return 0; | ||
795 | |||
796 | error_file_create: | ||
797 | destroy_wireless(); | ||
798 | |||
799 | error_no_rfk: | ||
800 | backlight_device_unregister(backlight_device); | ||
801 | |||
802 | error_no_backlight: | ||
803 | platform_device_unregister(sdev); | ||
804 | |||
805 | error_no_platform: | ||
806 | iounmap(sabi_iface); | ||
807 | |||
808 | error_no_signature: | ||
809 | iounmap(f0000_segment); | ||
810 | return -EINVAL; | ||
811 | } | ||
812 | |||
813 | static void __exit samsung_exit(void) | ||
814 | { | ||
815 | /* Turn off "Linux" mode in the BIOS */ | ||
816 | if (sabi_config->commands.set_linux != 0xff) | ||
817 | sabi_set_command(sabi_config->commands.set_linux, 0x80); | ||
818 | |||
819 | device_remove_file(&sdev->dev, &dev_attr_performance_level); | ||
820 | backlight_device_unregister(backlight_device); | ||
821 | destroy_wireless(); | ||
822 | iounmap(sabi_iface); | ||
823 | iounmap(f0000_segment); | ||
824 | platform_device_unregister(sdev); | ||
825 | } | ||
826 | |||
827 | module_init(samsung_init); | ||
828 | module_exit(samsung_exit); | ||
829 | |||
830 | MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>"); | ||
831 | MODULE_DESCRIPTION("Samsung Backlight driver"); | ||
832 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 13d8d63bcca9..e642f5f29504 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c | |||
@@ -71,8 +71,9 @@ | |||
71 | #endif | 71 | #endif |
72 | 72 | ||
73 | #define DRV_PFX "sony-laptop: " | 73 | #define DRV_PFX "sony-laptop: " |
74 | #define dprintk(msg...) do { \ | 74 | #define dprintk(msg...) do { \ |
75 | if (debug) printk(KERN_WARNING DRV_PFX msg); \ | 75 | if (debug) \ |
76 | pr_warn(DRV_PFX msg); \ | ||
76 | } while (0) | 77 | } while (0) |
77 | 78 | ||
78 | #define SONY_LAPTOP_DRIVER_VERSION "0.6" | 79 | #define SONY_LAPTOP_DRIVER_VERSION "0.6" |
@@ -124,6 +125,19 @@ MODULE_PARM_DESC(minor, | |||
124 | "default is -1 (automatic)"); | 125 | "default is -1 (automatic)"); |
125 | #endif | 126 | #endif |
126 | 127 | ||
128 | static int kbd_backlight; /* = 1 */ | ||
129 | module_param(kbd_backlight, int, 0444); | ||
130 | MODULE_PARM_DESC(kbd_backlight, | ||
131 | "set this to 0 to disable keyboard backlight, " | ||
132 | "1 to enable it (default: 0)"); | ||
133 | |||
134 | static int kbd_backlight_timeout; /* = 0 */ | ||
135 | module_param(kbd_backlight_timeout, int, 0444); | ||
136 | MODULE_PARM_DESC(kbd_backlight_timeout, | ||
137 | "set this to 0 to set the default 10 seconds timeout, " | ||
138 | "1 for 30 seconds, 2 for 60 seconds and 3 to disable timeout " | ||
139 | "(default: 0)"); | ||
140 | |||
127 | enum sony_nc_rfkill { | 141 | enum sony_nc_rfkill { |
128 | SONY_WIFI, | 142 | SONY_WIFI, |
129 | SONY_BLUETOOTH, | 143 | SONY_BLUETOOTH, |
@@ -402,7 +416,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device) | |||
402 | error = kfifo_alloc(&sony_laptop_input.fifo, | 416 | error = kfifo_alloc(&sony_laptop_input.fifo, |
403 | SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); | 417 | SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); |
404 | if (error) { | 418 | if (error) { |
405 | printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); | 419 | pr_err(DRV_PFX "kfifo_alloc failed\n"); |
406 | goto err_dec_users; | 420 | goto err_dec_users; |
407 | } | 421 | } |
408 | 422 | ||
@@ -591,7 +605,7 @@ struct sony_nc_value { | |||
591 | int value; /* current setting */ | 605 | int value; /* current setting */ |
592 | int valid; /* Has ever been set */ | 606 | int valid; /* Has ever been set */ |
593 | int debug; /* active only in debug mode ? */ | 607 | int debug; /* active only in debug mode ? */ |
594 | struct device_attribute devattr; /* sysfs atribute */ | 608 | struct device_attribute devattr; /* sysfs attribute */ |
595 | }; | 609 | }; |
596 | 610 | ||
597 | #define SNC_HANDLE_NAMES(_name, _values...) \ | 611 | #define SNC_HANDLE_NAMES(_name, _values...) \ |
@@ -686,7 +700,7 @@ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result) | |||
686 | return 0; | 700 | return 0; |
687 | } | 701 | } |
688 | 702 | ||
689 | printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n"); | 703 | pr_warn(DRV_PFX "acpi_callreadfunc failed\n"); |
690 | 704 | ||
691 | return -1; | 705 | return -1; |
692 | } | 706 | } |
@@ -712,7 +726,7 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value, | |||
712 | if (status == AE_OK) { | 726 | if (status == AE_OK) { |
713 | if (result != NULL) { | 727 | if (result != NULL) { |
714 | if (out_obj.type != ACPI_TYPE_INTEGER) { | 728 | if (out_obj.type != ACPI_TYPE_INTEGER) { |
715 | printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad " | 729 | pr_warn(DRV_PFX "acpi_evaluate_object bad " |
716 | "return type\n"); | 730 | "return type\n"); |
717 | return -1; | 731 | return -1; |
718 | } | 732 | } |
@@ -721,34 +735,103 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value, | |||
721 | return 0; | 735 | return 0; |
722 | } | 736 | } |
723 | 737 | ||
724 | printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n"); | 738 | pr_warn(DRV_PFX "acpi_evaluate_object failed\n"); |
725 | 739 | ||
726 | return -1; | 740 | return -1; |
727 | } | 741 | } |
728 | 742 | ||
729 | static int sony_find_snc_handle(int handle) | 743 | struct sony_nc_handles { |
744 | u16 cap[0x10]; | ||
745 | struct device_attribute devattr; | ||
746 | }; | ||
747 | |||
748 | static struct sony_nc_handles *handles; | ||
749 | |||
750 | static ssize_t sony_nc_handles_show(struct device *dev, | ||
751 | struct device_attribute *attr, char *buffer) | ||
752 | { | ||
753 | ssize_t len = 0; | ||
754 | int i; | ||
755 | |||
756 | for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { | ||
757 | len += snprintf(buffer + len, PAGE_SIZE - len, "0x%.4x ", | ||
758 | handles->cap[i]); | ||
759 | } | ||
760 | len += snprintf(buffer + len, PAGE_SIZE - len, "\n"); | ||
761 | |||
762 | return len; | ||
763 | } | ||
764 | |||
765 | static int sony_nc_handles_setup(struct platform_device *pd) | ||
730 | { | 766 | { |
731 | int i; | 767 | int i; |
732 | int result; | 768 | int result; |
733 | 769 | ||
734 | for (i = 0x20; i < 0x30; i++) { | 770 | handles = kzalloc(sizeof(*handles), GFP_KERNEL); |
735 | acpi_callsetfunc(sony_nc_acpi_handle, "SN00", i, &result); | 771 | if (!handles) |
736 | if (result == handle) | 772 | return -ENOMEM; |
737 | return i-0x20; | 773 | |
774 | sysfs_attr_init(&handles->devattr.attr); | ||
775 | handles->devattr.attr.name = "handles"; | ||
776 | handles->devattr.attr.mode = S_IRUGO; | ||
777 | handles->devattr.show = sony_nc_handles_show; | ||
778 | |||
779 | for (i = 0; i < ARRAY_SIZE(handles->cap); i++) { | ||
780 | if (!acpi_callsetfunc(sony_nc_acpi_handle, | ||
781 | "SN00", i + 0x20, &result)) { | ||
782 | dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n", | ||
783 | result, i); | ||
784 | handles->cap[i] = result; | ||
785 | } | ||
786 | } | ||
787 | |||
788 | /* allow reading capabilities via sysfs */ | ||
789 | if (device_create_file(&pd->dev, &handles->devattr)) { | ||
790 | kfree(handles); | ||
791 | handles = NULL; | ||
792 | return -1; | ||
793 | } | ||
794 | |||
795 | return 0; | ||
796 | } | ||
797 | |||
798 | static int sony_nc_handles_cleanup(struct platform_device *pd) | ||
799 | { | ||
800 | if (handles) { | ||
801 | device_remove_file(&pd->dev, &handles->devattr); | ||
802 | kfree(handles); | ||
803 | handles = NULL; | ||
738 | } | 804 | } |
805 | return 0; | ||
806 | } | ||
739 | 807 | ||
808 | static int sony_find_snc_handle(int handle) | ||
809 | { | ||
810 | int i; | ||
811 | for (i = 0; i < 0x10; i++) { | ||
812 | if (handles->cap[i] == handle) { | ||
813 | dprintk("found handle 0x%.4x (offset: 0x%.2x)\n", | ||
814 | handle, i); | ||
815 | return i; | ||
816 | } | ||
817 | } | ||
818 | dprintk("handle 0x%.4x not found\n", handle); | ||
740 | return -1; | 819 | return -1; |
741 | } | 820 | } |
742 | 821 | ||
743 | static int sony_call_snc_handle(int handle, int argument, int *result) | 822 | static int sony_call_snc_handle(int handle, int argument, int *result) |
744 | { | 823 | { |
824 | int ret = 0; | ||
745 | int offset = sony_find_snc_handle(handle); | 825 | int offset = sony_find_snc_handle(handle); |
746 | 826 | ||
747 | if (offset < 0) | 827 | if (offset < 0) |
748 | return -1; | 828 | return -1; |
749 | 829 | ||
750 | return acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument, | 830 | ret = acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument, |
751 | result); | 831 | result); |
832 | dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", offset | argument, | ||
833 | *result); | ||
834 | return ret; | ||
752 | } | 835 | } |
753 | 836 | ||
754 | /* | 837 | /* |
@@ -857,11 +940,39 @@ static int sony_backlight_get_brightness(struct backlight_device *bd) | |||
857 | return value - 1; | 940 | return value - 1; |
858 | } | 941 | } |
859 | 942 | ||
860 | static struct backlight_device *sony_backlight_device; | 943 | static int sony_nc_get_brightness_ng(struct backlight_device *bd) |
944 | { | ||
945 | int result; | ||
946 | int *handle = (int *)bl_get_data(bd); | ||
947 | |||
948 | sony_call_snc_handle(*handle, 0x0200, &result); | ||
949 | |||
950 | return result & 0xff; | ||
951 | } | ||
952 | |||
953 | static int sony_nc_update_status_ng(struct backlight_device *bd) | ||
954 | { | ||
955 | int value, result; | ||
956 | int *handle = (int *)bl_get_data(bd); | ||
957 | |||
958 | value = bd->props.brightness; | ||
959 | sony_call_snc_handle(*handle, 0x0100 | (value << 16), &result); | ||
960 | |||
961 | return sony_nc_get_brightness_ng(bd); | ||
962 | } | ||
963 | |||
861 | static const struct backlight_ops sony_backlight_ops = { | 964 | static const struct backlight_ops sony_backlight_ops = { |
965 | .options = BL_CORE_SUSPENDRESUME, | ||
862 | .update_status = sony_backlight_update_status, | 966 | .update_status = sony_backlight_update_status, |
863 | .get_brightness = sony_backlight_get_brightness, | 967 | .get_brightness = sony_backlight_get_brightness, |
864 | }; | 968 | }; |
969 | static const struct backlight_ops sony_backlight_ng_ops = { | ||
970 | .options = BL_CORE_SUSPENDRESUME, | ||
971 | .update_status = sony_nc_update_status_ng, | ||
972 | .get_brightness = sony_nc_get_brightness_ng, | ||
973 | }; | ||
974 | static int backlight_ng_handle; | ||
975 | static struct backlight_device *sony_backlight_device; | ||
865 | 976 | ||
866 | /* | 977 | /* |
867 | * New SNC-only Vaios event mapping to driver known keys | 978 | * New SNC-only Vaios event mapping to driver known keys |
@@ -972,7 +1083,7 @@ static void sony_nc_notify(struct acpi_device *device, u32 event) | |||
972 | } | 1083 | } |
973 | 1084 | ||
974 | if (!key_event->data) | 1085 | if (!key_event->data) |
975 | printk(KERN_INFO DRV_PFX | 1086 | pr_info(DRV_PFX |
976 | "Unknown event: 0x%x 0x%x\n", | 1087 | "Unknown event: 0x%x 0x%x\n", |
977 | key_handle, | 1088 | key_handle, |
978 | ev); | 1089 | ev); |
@@ -996,7 +1107,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level, | |||
996 | struct acpi_device_info *info; | 1107 | struct acpi_device_info *info; |
997 | 1108 | ||
998 | if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) { | 1109 | if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) { |
999 | printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n", | 1110 | pr_warn(DRV_PFX "method: name: %4.4s, args %X\n", |
1000 | (char *)&info->name, info->param_count); | 1111 | (char *)&info->name, info->param_count); |
1001 | 1112 | ||
1002 | kfree(info); | 1113 | kfree(info); |
@@ -1037,7 +1148,7 @@ static int sony_nc_resume(struct acpi_device *device) | |||
1037 | ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, | 1148 | ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset, |
1038 | item->value, NULL); | 1149 | item->value, NULL); |
1039 | if (ret < 0) { | 1150 | if (ret < 0) { |
1040 | printk("%s: %d\n", __func__, ret); | 1151 | pr_err(DRV_PFX "%s: %d\n", __func__, ret); |
1041 | break; | 1152 | break; |
1042 | } | 1153 | } |
1043 | } | 1154 | } |
@@ -1054,11 +1165,6 @@ static int sony_nc_resume(struct acpi_device *device) | |||
1054 | sony_nc_function_setup(device); | 1165 | sony_nc_function_setup(device); |
1055 | } | 1166 | } |
1056 | 1167 | ||
1057 | /* set the last requested brightness level */ | ||
1058 | if (sony_backlight_device && | ||
1059 | sony_backlight_update_status(sony_backlight_device) < 0) | ||
1060 | printk(KERN_WARNING DRV_PFX "unable to restore brightness level\n"); | ||
1061 | |||
1062 | /* re-read rfkill state */ | 1168 | /* re-read rfkill state */ |
1063 | sony_nc_rfkill_update(); | 1169 | sony_nc_rfkill_update(); |
1064 | 1170 | ||
@@ -1206,12 +1312,12 @@ static void sony_nc_rfkill_setup(struct acpi_device *device) | |||
1206 | 1312 | ||
1207 | device_enum = (union acpi_object *) buffer.pointer; | 1313 | device_enum = (union acpi_object *) buffer.pointer; |
1208 | if (!device_enum) { | 1314 | if (!device_enum) { |
1209 | pr_err("Invalid SN06 return object\n"); | 1315 | pr_err(DRV_PFX "No SN06 return object."); |
1210 | goto out_no_enum; | 1316 | goto out_no_enum; |
1211 | } | 1317 | } |
1212 | if (device_enum->type != ACPI_TYPE_BUFFER) { | 1318 | if (device_enum->type != ACPI_TYPE_BUFFER) { |
1213 | pr_err("Invalid SN06 return object type 0x%.2x\n", | 1319 | pr_err(DRV_PFX "Invalid SN06 return object 0x%.2x\n", |
1214 | device_enum->type); | 1320 | device_enum->type); |
1215 | goto out_no_enum; | 1321 | goto out_no_enum; |
1216 | } | 1322 | } |
1217 | 1323 | ||
@@ -1245,6 +1351,209 @@ out_no_enum: | |||
1245 | return; | 1351 | return; |
1246 | } | 1352 | } |
1247 | 1353 | ||
1354 | /* Keyboard backlight feature */ | ||
1355 | #define KBDBL_HANDLER 0x137 | ||
1356 | #define KBDBL_PRESENT 0xB00 | ||
1357 | #define SET_MODE 0xC00 | ||
1358 | #define SET_TIMEOUT 0xE00 | ||
1359 | |||
1360 | struct kbd_backlight { | ||
1361 | int mode; | ||
1362 | int timeout; | ||
1363 | struct device_attribute mode_attr; | ||
1364 | struct device_attribute timeout_attr; | ||
1365 | }; | ||
1366 | |||
1367 | static struct kbd_backlight *kbdbl_handle; | ||
1368 | |||
1369 | static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value) | ||
1370 | { | ||
1371 | int result; | ||
1372 | |||
1373 | if (value > 1) | ||
1374 | return -EINVAL; | ||
1375 | |||
1376 | if (sony_call_snc_handle(KBDBL_HANDLER, | ||
1377 | (value << 0x10) | SET_MODE, &result)) | ||
1378 | return -EIO; | ||
1379 | |||
1380 | kbdbl_handle->mode = value; | ||
1381 | |||
1382 | return 0; | ||
1383 | } | ||
1384 | |||
1385 | static ssize_t sony_nc_kbd_backlight_mode_store(struct device *dev, | ||
1386 | struct device_attribute *attr, | ||
1387 | const char *buffer, size_t count) | ||
1388 | { | ||
1389 | int ret = 0; | ||
1390 | unsigned long value; | ||
1391 | |||
1392 | if (count > 31) | ||
1393 | return -EINVAL; | ||
1394 | |||
1395 | if (strict_strtoul(buffer, 10, &value)) | ||
1396 | return -EINVAL; | ||
1397 | |||
1398 | ret = __sony_nc_kbd_backlight_mode_set(value); | ||
1399 | if (ret < 0) | ||
1400 | return ret; | ||
1401 | |||
1402 | return count; | ||
1403 | } | ||
1404 | |||
1405 | static ssize_t sony_nc_kbd_backlight_mode_show(struct device *dev, | ||
1406 | struct device_attribute *attr, char *buffer) | ||
1407 | { | ||
1408 | ssize_t count = 0; | ||
1409 | count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_handle->mode); | ||
1410 | return count; | ||
1411 | } | ||
1412 | |||
1413 | static int __sony_nc_kbd_backlight_timeout_set(u8 value) | ||
1414 | { | ||
1415 | int result; | ||
1416 | |||
1417 | if (value > 3) | ||
1418 | return -EINVAL; | ||
1419 | |||
1420 | if (sony_call_snc_handle(KBDBL_HANDLER, | ||
1421 | (value << 0x10) | SET_TIMEOUT, &result)) | ||
1422 | return -EIO; | ||
1423 | |||
1424 | kbdbl_handle->timeout = value; | ||
1425 | |||
1426 | return 0; | ||
1427 | } | ||
1428 | |||
1429 | static ssize_t sony_nc_kbd_backlight_timeout_store(struct device *dev, | ||
1430 | struct device_attribute *attr, | ||
1431 | const char *buffer, size_t count) | ||
1432 | { | ||
1433 | int ret = 0; | ||
1434 | unsigned long value; | ||
1435 | |||
1436 | if (count > 31) | ||
1437 | return -EINVAL; | ||
1438 | |||
1439 | if (strict_strtoul(buffer, 10, &value)) | ||
1440 | return -EINVAL; | ||
1441 | |||
1442 | ret = __sony_nc_kbd_backlight_timeout_set(value); | ||
1443 | if (ret < 0) | ||
1444 | return ret; | ||
1445 | |||
1446 | return count; | ||
1447 | } | ||
1448 | |||
1449 | static ssize_t sony_nc_kbd_backlight_timeout_show(struct device *dev, | ||
1450 | struct device_attribute *attr, char *buffer) | ||
1451 | { | ||
1452 | ssize_t count = 0; | ||
1453 | count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_handle->timeout); | ||
1454 | return count; | ||
1455 | } | ||
1456 | |||
1457 | static int sony_nc_kbd_backlight_setup(struct platform_device *pd) | ||
1458 | { | ||
1459 | int result; | ||
1460 | |||
1461 | if (sony_call_snc_handle(0x137, KBDBL_PRESENT, &result)) | ||
1462 | return 0; | ||
1463 | if (!(result & 0x02)) | ||
1464 | return 0; | ||
1465 | |||
1466 | kbdbl_handle = kzalloc(sizeof(*kbdbl_handle), GFP_KERNEL); | ||
1467 | if (!kbdbl_handle) | ||
1468 | return -ENOMEM; | ||
1469 | |||
1470 | sysfs_attr_init(&kbdbl_handle->mode_attr.attr); | ||
1471 | kbdbl_handle->mode_attr.attr.name = "kbd_backlight"; | ||
1472 | kbdbl_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR; | ||
1473 | kbdbl_handle->mode_attr.show = sony_nc_kbd_backlight_mode_show; | ||
1474 | kbdbl_handle->mode_attr.store = sony_nc_kbd_backlight_mode_store; | ||
1475 | |||
1476 | sysfs_attr_init(&kbdbl_handle->timeout_attr.attr); | ||
1477 | kbdbl_handle->timeout_attr.attr.name = "kbd_backlight_timeout"; | ||
1478 | kbdbl_handle->timeout_attr.attr.mode = S_IRUGO | S_IWUSR; | ||
1479 | kbdbl_handle->timeout_attr.show = sony_nc_kbd_backlight_timeout_show; | ||
1480 | kbdbl_handle->timeout_attr.store = sony_nc_kbd_backlight_timeout_store; | ||
1481 | |||
1482 | if (device_create_file(&pd->dev, &kbdbl_handle->mode_attr)) | ||
1483 | goto outkzalloc; | ||
1484 | |||
1485 | if (device_create_file(&pd->dev, &kbdbl_handle->timeout_attr)) | ||
1486 | goto outmode; | ||
1487 | |||
1488 | __sony_nc_kbd_backlight_mode_set(kbd_backlight); | ||
1489 | __sony_nc_kbd_backlight_timeout_set(kbd_backlight_timeout); | ||
1490 | |||
1491 | return 0; | ||
1492 | |||
1493 | outmode: | ||
1494 | device_remove_file(&pd->dev, &kbdbl_handle->mode_attr); | ||
1495 | outkzalloc: | ||
1496 | kfree(kbdbl_handle); | ||
1497 | kbdbl_handle = NULL; | ||
1498 | return -1; | ||
1499 | } | ||
1500 | |||
1501 | static int sony_nc_kbd_backlight_cleanup(struct platform_device *pd) | ||
1502 | { | ||
1503 | if (kbdbl_handle) { | ||
1504 | device_remove_file(&pd->dev, &kbdbl_handle->mode_attr); | ||
1505 | device_remove_file(&pd->dev, &kbdbl_handle->timeout_attr); | ||
1506 | kfree(kbdbl_handle); | ||
1507 | } | ||
1508 | return 0; | ||
1509 | } | ||
1510 | |||
1511 | static void sony_nc_backlight_setup(void) | ||
1512 | { | ||
1513 | acpi_handle unused; | ||
1514 | int max_brightness = 0; | ||
1515 | const struct backlight_ops *ops = NULL; | ||
1516 | struct backlight_properties props; | ||
1517 | |||
1518 | if (sony_find_snc_handle(0x12f) != -1) { | ||
1519 | backlight_ng_handle = 0x12f; | ||
1520 | ops = &sony_backlight_ng_ops; | ||
1521 | max_brightness = 0xff; | ||
1522 | |||
1523 | } else if (sony_find_snc_handle(0x137) != -1) { | ||
1524 | backlight_ng_handle = 0x137; | ||
1525 | ops = &sony_backlight_ng_ops; | ||
1526 | max_brightness = 0xff; | ||
1527 | |||
1528 | } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", | ||
1529 | &unused))) { | ||
1530 | ops = &sony_backlight_ops; | ||
1531 | max_brightness = SONY_MAX_BRIGHTNESS - 1; | ||
1532 | |||
1533 | } else | ||
1534 | return; | ||
1535 | |||
1536 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
1537 | props.type = BACKLIGHT_PLATFORM; | ||
1538 | props.max_brightness = max_brightness; | ||
1539 | sony_backlight_device = backlight_device_register("sony", NULL, | ||
1540 | &backlight_ng_handle, | ||
1541 | ops, &props); | ||
1542 | |||
1543 | if (IS_ERR(sony_backlight_device)) { | ||
1544 | pr_warning(DRV_PFX "unable to register backlight device\n"); | ||
1545 | sony_backlight_device = NULL; | ||
1546 | } else | ||
1547 | sony_backlight_device->props.brightness = | ||
1548 | ops->get_brightness(sony_backlight_device); | ||
1549 | } | ||
1550 | |||
1551 | static void sony_nc_backlight_cleanup(void) | ||
1552 | { | ||
1553 | if (sony_backlight_device) | ||
1554 | backlight_device_unregister(sony_backlight_device); | ||
1555 | } | ||
1556 | |||
1248 | static int sony_nc_add(struct acpi_device *device) | 1557 | static int sony_nc_add(struct acpi_device *device) |
1249 | { | 1558 | { |
1250 | acpi_status status; | 1559 | acpi_status status; |
@@ -1252,8 +1561,8 @@ static int sony_nc_add(struct acpi_device *device) | |||
1252 | acpi_handle handle; | 1561 | acpi_handle handle; |
1253 | struct sony_nc_value *item; | 1562 | struct sony_nc_value *item; |
1254 | 1563 | ||
1255 | printk(KERN_INFO DRV_PFX "%s v%s.\n", | 1564 | pr_info(DRV_PFX "%s v%s.\n", SONY_NC_DRIVER_NAME, |
1256 | SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); | 1565 | SONY_LAPTOP_DRIVER_VERSION); |
1257 | 1566 | ||
1258 | sony_nc_acpi_device = device; | 1567 | sony_nc_acpi_device = device; |
1259 | strcpy(acpi_device_class(device), "sony/hotkey"); | 1568 | strcpy(acpi_device_class(device), "sony/hotkey"); |
@@ -1269,13 +1578,18 @@ static int sony_nc_add(struct acpi_device *device) | |||
1269 | goto outwalk; | 1578 | goto outwalk; |
1270 | } | 1579 | } |
1271 | 1580 | ||
1581 | result = sony_pf_add(); | ||
1582 | if (result) | ||
1583 | goto outpresent; | ||
1584 | |||
1272 | if (debug) { | 1585 | if (debug) { |
1273 | status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle, | 1586 | status = acpi_walk_namespace(ACPI_TYPE_METHOD, |
1274 | 1, sony_walk_callback, NULL, NULL, NULL); | 1587 | sony_nc_acpi_handle, 1, sony_walk_callback, |
1588 | NULL, NULL, NULL); | ||
1275 | if (ACPI_FAILURE(status)) { | 1589 | if (ACPI_FAILURE(status)) { |
1276 | printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n"); | 1590 | pr_warn(DRV_PFX "unable to walk acpi resources\n"); |
1277 | result = -ENODEV; | 1591 | result = -ENODEV; |
1278 | goto outwalk; | 1592 | goto outpresent; |
1279 | } | 1593 | } |
1280 | } | 1594 | } |
1281 | 1595 | ||
@@ -1288,6 +1602,12 @@ static int sony_nc_add(struct acpi_device *device) | |||
1288 | if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00", | 1602 | if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00", |
1289 | &handle))) { | 1603 | &handle))) { |
1290 | dprintk("Doing SNC setup\n"); | 1604 | dprintk("Doing SNC setup\n"); |
1605 | result = sony_nc_handles_setup(sony_pf_device); | ||
1606 | if (result) | ||
1607 | goto outpresent; | ||
1608 | result = sony_nc_kbd_backlight_setup(sony_pf_device); | ||
1609 | if (result) | ||
1610 | goto outsnc; | ||
1291 | sony_nc_function_setup(device); | 1611 | sony_nc_function_setup(device); |
1292 | sony_nc_rfkill_setup(device); | 1612 | sony_nc_rfkill_setup(device); |
1293 | } | 1613 | } |
@@ -1295,40 +1615,17 @@ static int sony_nc_add(struct acpi_device *device) | |||
1295 | /* setup input devices and helper fifo */ | 1615 | /* setup input devices and helper fifo */ |
1296 | result = sony_laptop_setup_input(device); | 1616 | result = sony_laptop_setup_input(device); |
1297 | if (result) { | 1617 | if (result) { |
1298 | printk(KERN_ERR DRV_PFX | 1618 | pr_err(DRV_PFX "Unable to create input devices.\n"); |
1299 | "Unable to create input devices.\n"); | 1619 | goto outkbdbacklight; |
1300 | goto outwalk; | ||
1301 | } | 1620 | } |
1302 | 1621 | ||
1303 | if (acpi_video_backlight_support()) { | 1622 | if (acpi_video_backlight_support()) { |
1304 | printk(KERN_INFO DRV_PFX "brightness ignored, must be " | 1623 | pr_info(DRV_PFX "brightness ignored, must be " |
1305 | "controlled by ACPI video driver\n"); | 1624 | "controlled by ACPI video driver\n"); |
1306 | } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT", | 1625 | } else { |
1307 | &handle))) { | 1626 | sony_nc_backlight_setup(); |
1308 | struct backlight_properties props; | ||
1309 | memset(&props, 0, sizeof(struct backlight_properties)); | ||
1310 | props.type = BACKLIGHT_PLATFORM; | ||
1311 | props.max_brightness = SONY_MAX_BRIGHTNESS - 1; | ||
1312 | sony_backlight_device = backlight_device_register("sony", NULL, | ||
1313 | NULL, | ||
1314 | &sony_backlight_ops, | ||
1315 | &props); | ||
1316 | |||
1317 | if (IS_ERR(sony_backlight_device)) { | ||
1318 | printk(KERN_WARNING DRV_PFX "unable to register backlight device\n"); | ||
1319 | sony_backlight_device = NULL; | ||
1320 | } else { | ||
1321 | sony_backlight_device->props.brightness = | ||
1322 | sony_backlight_get_brightness | ||
1323 | (sony_backlight_device); | ||
1324 | } | ||
1325 | |||
1326 | } | 1627 | } |
1327 | 1628 | ||
1328 | result = sony_pf_add(); | ||
1329 | if (result) | ||
1330 | goto outbacklight; | ||
1331 | |||
1332 | /* create sony_pf sysfs attributes related to the SNC device */ | 1629 | /* create sony_pf sysfs attributes related to the SNC device */ |
1333 | for (item = sony_nc_values; item->name; ++item) { | 1630 | for (item = sony_nc_values; item->name; ++item) { |
1334 | 1631 | ||
@@ -1374,14 +1671,19 @@ static int sony_nc_add(struct acpi_device *device) | |||
1374 | for (item = sony_nc_values; item->name; ++item) { | 1671 | for (item = sony_nc_values; item->name; ++item) { |
1375 | device_remove_file(&sony_pf_device->dev, &item->devattr); | 1672 | device_remove_file(&sony_pf_device->dev, &item->devattr); |
1376 | } | 1673 | } |
1377 | sony_pf_remove(); | 1674 | sony_nc_backlight_cleanup(); |
1378 | |||
1379 | outbacklight: | ||
1380 | if (sony_backlight_device) | ||
1381 | backlight_device_unregister(sony_backlight_device); | ||
1382 | 1675 | ||
1383 | sony_laptop_remove_input(); | 1676 | sony_laptop_remove_input(); |
1384 | 1677 | ||
1678 | outkbdbacklight: | ||
1679 | sony_nc_kbd_backlight_cleanup(sony_pf_device); | ||
1680 | |||
1681 | outsnc: | ||
1682 | sony_nc_handles_cleanup(sony_pf_device); | ||
1683 | |||
1684 | outpresent: | ||
1685 | sony_pf_remove(); | ||
1686 | |||
1385 | outwalk: | 1687 | outwalk: |
1386 | sony_nc_rfkill_cleanup(); | 1688 | sony_nc_rfkill_cleanup(); |
1387 | return result; | 1689 | return result; |
@@ -1391,8 +1693,7 @@ static int sony_nc_remove(struct acpi_device *device, int type) | |||
1391 | { | 1693 | { |
1392 | struct sony_nc_value *item; | 1694 | struct sony_nc_value *item; |
1393 | 1695 | ||
1394 | if (sony_backlight_device) | 1696 | sony_nc_backlight_cleanup(); |
1395 | backlight_device_unregister(sony_backlight_device); | ||
1396 | 1697 | ||
1397 | sony_nc_acpi_device = NULL; | 1698 | sony_nc_acpi_device = NULL; |
1398 | 1699 | ||
@@ -1400,6 +1701,8 @@ static int sony_nc_remove(struct acpi_device *device, int type) | |||
1400 | device_remove_file(&sony_pf_device->dev, &item->devattr); | 1701 | device_remove_file(&sony_pf_device->dev, &item->devattr); |
1401 | } | 1702 | } |
1402 | 1703 | ||
1704 | sony_nc_kbd_backlight_cleanup(sony_pf_device); | ||
1705 | sony_nc_handles_cleanup(sony_pf_device); | ||
1403 | sony_pf_remove(); | 1706 | sony_pf_remove(); |
1404 | sony_laptop_remove_input(); | 1707 | sony_laptop_remove_input(); |
1405 | sony_nc_rfkill_cleanup(); | 1708 | sony_nc_rfkill_cleanup(); |
@@ -1438,7 +1741,6 @@ static struct acpi_driver sony_nc_driver = { | |||
1438 | #define SONYPI_DEVICE_TYPE1 0x00000001 | 1741 | #define SONYPI_DEVICE_TYPE1 0x00000001 |
1439 | #define SONYPI_DEVICE_TYPE2 0x00000002 | 1742 | #define SONYPI_DEVICE_TYPE2 0x00000002 |
1440 | #define SONYPI_DEVICE_TYPE3 0x00000004 | 1743 | #define SONYPI_DEVICE_TYPE3 0x00000004 |
1441 | #define SONYPI_DEVICE_TYPE4 0x00000008 | ||
1442 | 1744 | ||
1443 | #define SONYPI_TYPE1_OFFSET 0x04 | 1745 | #define SONYPI_TYPE1_OFFSET 0x04 |
1444 | #define SONYPI_TYPE2_OFFSET 0x12 | 1746 | #define SONYPI_TYPE2_OFFSET 0x12 |
@@ -1584,8 +1886,8 @@ static struct sonypi_event sonypi_blueev[] = { | |||
1584 | 1886 | ||
1585 | /* The set of possible wireless events */ | 1887 | /* The set of possible wireless events */ |
1586 | static struct sonypi_event sonypi_wlessev[] = { | 1888 | static struct sonypi_event sonypi_wlessev[] = { |
1587 | { 0x59, SONYPI_EVENT_WIRELESS_ON }, | 1889 | { 0x59, SONYPI_EVENT_IGNORE }, |
1588 | { 0x5a, SONYPI_EVENT_WIRELESS_OFF }, | 1890 | { 0x5a, SONYPI_EVENT_IGNORE }, |
1589 | { 0, 0 } | 1891 | { 0, 0 } |
1590 | }; | 1892 | }; |
1591 | 1893 | ||
@@ -1842,7 +2144,7 @@ out: | |||
1842 | if (pcidev) | 2144 | if (pcidev) |
1843 | pci_dev_put(pcidev); | 2145 | pci_dev_put(pcidev); |
1844 | 2146 | ||
1845 | printk(KERN_INFO DRV_PFX "detected Type%d model\n", | 2147 | pr_info(DRV_PFX "detected Type%d model\n", |
1846 | dev->model == SONYPI_DEVICE_TYPE1 ? 1 : | 2148 | dev->model == SONYPI_DEVICE_TYPE1 ? 1 : |
1847 | dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3); | 2149 | dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3); |
1848 | } | 2150 | } |
@@ -1890,7 +2192,7 @@ static int __sony_pic_camera_ready(void) | |||
1890 | static int __sony_pic_camera_off(void) | 2192 | static int __sony_pic_camera_off(void) |
1891 | { | 2193 | { |
1892 | if (!camera) { | 2194 | if (!camera) { |
1893 | printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); | 2195 | pr_warn(DRV_PFX "camera control not enabled\n"); |
1894 | return -ENODEV; | 2196 | return -ENODEV; |
1895 | } | 2197 | } |
1896 | 2198 | ||
@@ -1910,7 +2212,7 @@ static int __sony_pic_camera_on(void) | |||
1910 | int i, j, x; | 2212 | int i, j, x; |
1911 | 2213 | ||
1912 | if (!camera) { | 2214 | if (!camera) { |
1913 | printk(KERN_WARNING DRV_PFX "camera control not enabled\n"); | 2215 | pr_warn(DRV_PFX "camera control not enabled\n"); |
1914 | return -ENODEV; | 2216 | return -ENODEV; |
1915 | } | 2217 | } |
1916 | 2218 | ||
@@ -1933,7 +2235,7 @@ static int __sony_pic_camera_on(void) | |||
1933 | } | 2235 | } |
1934 | 2236 | ||
1935 | if (j == 0) { | 2237 | if (j == 0) { |
1936 | printk(KERN_WARNING DRV_PFX "failed to power on camera\n"); | 2238 | pr_warn(DRV_PFX "failed to power on camera\n"); |
1937 | return -ENODEV; | 2239 | return -ENODEV; |
1938 | } | 2240 | } |
1939 | 2241 | ||
@@ -1989,7 +2291,7 @@ int sony_pic_camera_command(int command, u8 value) | |||
1989 | ITERATIONS_SHORT); | 2291 | ITERATIONS_SHORT); |
1990 | break; | 2292 | break; |
1991 | default: | 2293 | default: |
1992 | printk(KERN_ERR DRV_PFX "sony_pic_camera_command invalid: %d\n", | 2294 | pr_err(DRV_PFX "sony_pic_camera_command invalid: %d\n", |
1993 | command); | 2295 | command); |
1994 | break; | 2296 | break; |
1995 | } | 2297 | } |
@@ -2396,7 +2698,7 @@ static int sonypi_compat_init(void) | |||
2396 | error = | 2698 | error = |
2397 | kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); | 2699 | kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL); |
2398 | if (error) { | 2700 | if (error) { |
2399 | printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n"); | 2701 | pr_err(DRV_PFX "kfifo_alloc failed\n"); |
2400 | return error; | 2702 | return error; |
2401 | } | 2703 | } |
2402 | 2704 | ||
@@ -2406,11 +2708,11 @@ static int sonypi_compat_init(void) | |||
2406 | sonypi_misc_device.minor = minor; | 2708 | sonypi_misc_device.minor = minor; |
2407 | error = misc_register(&sonypi_misc_device); | 2709 | error = misc_register(&sonypi_misc_device); |
2408 | if (error) { | 2710 | if (error) { |
2409 | printk(KERN_ERR DRV_PFX "misc_register failed\n"); | 2711 | pr_err(DRV_PFX "misc_register failed\n"); |
2410 | goto err_free_kfifo; | 2712 | goto err_free_kfifo; |
2411 | } | 2713 | } |
2412 | if (minor == -1) | 2714 | if (minor == -1) |
2413 | printk(KERN_INFO DRV_PFX "device allocated minor is %d\n", | 2715 | pr_info(DRV_PFX "device allocated minor is %d\n", |
2414 | sonypi_misc_device.minor); | 2716 | sonypi_misc_device.minor); |
2415 | 2717 | ||
2416 | return 0; | 2718 | return 0; |
@@ -2470,8 +2772,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) | |||
2470 | } | 2772 | } |
2471 | for (i = 0; i < p->interrupt_count; i++) { | 2773 | for (i = 0; i < p->interrupt_count; i++) { |
2472 | if (!p->interrupts[i]) { | 2774 | if (!p->interrupts[i]) { |
2473 | printk(KERN_WARNING DRV_PFX | 2775 | pr_warn(DRV_PFX "Invalid IRQ %d\n", |
2474 | "Invalid IRQ %d\n", | ||
2475 | p->interrupts[i]); | 2776 | p->interrupts[i]); |
2476 | continue; | 2777 | continue; |
2477 | } | 2778 | } |
@@ -2510,7 +2811,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) | |||
2510 | ioport->io2.address_length); | 2811 | ioport->io2.address_length); |
2511 | } | 2812 | } |
2512 | else { | 2813 | else { |
2513 | printk(KERN_ERR DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n"); | 2814 | pr_err(DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n"); |
2514 | return AE_ERROR; | 2815 | return AE_ERROR; |
2515 | } | 2816 | } |
2516 | return AE_OK; | 2817 | return AE_OK; |
@@ -2538,7 +2839,7 @@ static int sony_pic_possible_resources(struct acpi_device *device) | |||
2538 | dprintk("Evaluating _STA\n"); | 2839 | dprintk("Evaluating _STA\n"); |
2539 | result = acpi_bus_get_status(device); | 2840 | result = acpi_bus_get_status(device); |
2540 | if (result) { | 2841 | if (result) { |
2541 | printk(KERN_WARNING DRV_PFX "Unable to read status\n"); | 2842 | pr_warn(DRV_PFX "Unable to read status\n"); |
2542 | goto end; | 2843 | goto end; |
2543 | } | 2844 | } |
2544 | 2845 | ||
@@ -2554,8 +2855,7 @@ static int sony_pic_possible_resources(struct acpi_device *device) | |||
2554 | status = acpi_walk_resources(device->handle, METHOD_NAME__PRS, | 2855 | status = acpi_walk_resources(device->handle, METHOD_NAME__PRS, |
2555 | sony_pic_read_possible_resource, &spic_dev); | 2856 | sony_pic_read_possible_resource, &spic_dev); |
2556 | if (ACPI_FAILURE(status)) { | 2857 | if (ACPI_FAILURE(status)) { |
2557 | printk(KERN_WARNING DRV_PFX | 2858 | pr_warn(DRV_PFX "Failure evaluating %s\n", |
2558 | "Failure evaluating %s\n", | ||
2559 | METHOD_NAME__PRS); | 2859 | METHOD_NAME__PRS); |
2560 | result = -ENODEV; | 2860 | result = -ENODEV; |
2561 | } | 2861 | } |
@@ -2669,7 +2969,7 @@ static int sony_pic_enable(struct acpi_device *device, | |||
2669 | 2969 | ||
2670 | /* check for total failure */ | 2970 | /* check for total failure */ |
2671 | if (ACPI_FAILURE(status)) { | 2971 | if (ACPI_FAILURE(status)) { |
2672 | printk(KERN_ERR DRV_PFX "Error evaluating _SRS\n"); | 2972 | pr_err(DRV_PFX "Error evaluating _SRS\n"); |
2673 | result = -ENODEV; | 2973 | result = -ENODEV; |
2674 | goto end; | 2974 | goto end; |
2675 | } | 2975 | } |
@@ -2725,6 +3025,9 @@ static irqreturn_t sony_pic_irq(int irq, void *dev_id) | |||
2725 | if (ev == dev->event_types[i].events[j].data) { | 3025 | if (ev == dev->event_types[i].events[j].data) { |
2726 | device_event = | 3026 | device_event = |
2727 | dev->event_types[i].events[j].event; | 3027 | dev->event_types[i].events[j].event; |
3028 | /* some events may require ignoring */ | ||
3029 | if (!device_event) | ||
3030 | return IRQ_HANDLED; | ||
2728 | goto found; | 3031 | goto found; |
2729 | } | 3032 | } |
2730 | } | 3033 | } |
@@ -2744,7 +3047,6 @@ found: | |||
2744 | sony_laptop_report_input_event(device_event); | 3047 | sony_laptop_report_input_event(device_event); |
2745 | acpi_bus_generate_proc_event(dev->acpi_dev, 1, device_event); | 3048 | acpi_bus_generate_proc_event(dev->acpi_dev, 1, device_event); |
2746 | sonypi_compat_report_event(device_event); | 3049 | sonypi_compat_report_event(device_event); |
2747 | |||
2748 | return IRQ_HANDLED; | 3050 | return IRQ_HANDLED; |
2749 | } | 3051 | } |
2750 | 3052 | ||
@@ -2759,7 +3061,7 @@ static int sony_pic_remove(struct acpi_device *device, int type) | |||
2759 | struct sony_pic_irq *irq, *tmp_irq; | 3061 | struct sony_pic_irq *irq, *tmp_irq; |
2760 | 3062 | ||
2761 | if (sony_pic_disable(device)) { | 3063 | if (sony_pic_disable(device)) { |
2762 | printk(KERN_ERR DRV_PFX "Couldn't disable device.\n"); | 3064 | pr_err(DRV_PFX "Couldn't disable device.\n"); |
2763 | return -ENXIO; | 3065 | return -ENXIO; |
2764 | } | 3066 | } |
2765 | 3067 | ||
@@ -2799,8 +3101,8 @@ static int sony_pic_add(struct acpi_device *device) | |||
2799 | struct sony_pic_ioport *io, *tmp_io; | 3101 | struct sony_pic_ioport *io, *tmp_io; |
2800 | struct sony_pic_irq *irq, *tmp_irq; | 3102 | struct sony_pic_irq *irq, *tmp_irq; |
2801 | 3103 | ||
2802 | printk(KERN_INFO DRV_PFX "%s v%s.\n", | 3104 | pr_info(DRV_PFX "%s v%s.\n", SONY_PIC_DRIVER_NAME, |
2803 | SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION); | 3105 | SONY_LAPTOP_DRIVER_VERSION); |
2804 | 3106 | ||
2805 | spic_dev.acpi_dev = device; | 3107 | spic_dev.acpi_dev = device; |
2806 | strcpy(acpi_device_class(device), "sony/hotkey"); | 3108 | strcpy(acpi_device_class(device), "sony/hotkey"); |
@@ -2810,16 +3112,14 @@ static int sony_pic_add(struct acpi_device *device) | |||
2810 | /* read _PRS resources */ | 3112 | /* read _PRS resources */ |
2811 | result = sony_pic_possible_resources(device); | 3113 | result = sony_pic_possible_resources(device); |
2812 | if (result) { | 3114 | if (result) { |
2813 | printk(KERN_ERR DRV_PFX | 3115 | pr_err(DRV_PFX "Unable to read possible resources.\n"); |
2814 | "Unable to read possible resources.\n"); | ||
2815 | goto err_free_resources; | 3116 | goto err_free_resources; |
2816 | } | 3117 | } |
2817 | 3118 | ||
2818 | /* setup input devices and helper fifo */ | 3119 | /* setup input devices and helper fifo */ |
2819 | result = sony_laptop_setup_input(device); | 3120 | result = sony_laptop_setup_input(device); |
2820 | if (result) { | 3121 | if (result) { |
2821 | printk(KERN_ERR DRV_PFX | 3122 | pr_err(DRV_PFX "Unable to create input devices.\n"); |
2822 | "Unable to create input devices.\n"); | ||
2823 | goto err_free_resources; | 3123 | goto err_free_resources; |
2824 | } | 3124 | } |
2825 | 3125 | ||
@@ -2829,7 +3129,7 @@ static int sony_pic_add(struct acpi_device *device) | |||
2829 | /* request io port */ | 3129 | /* request io port */ |
2830 | list_for_each_entry_reverse(io, &spic_dev.ioports, list) { | 3130 | list_for_each_entry_reverse(io, &spic_dev.ioports, list) { |
2831 | if (request_region(io->io1.minimum, io->io1.address_length, | 3131 | if (request_region(io->io1.minimum, io->io1.address_length, |
2832 | "Sony Programable I/O Device")) { | 3132 | "Sony Programmable I/O Device")) { |
2833 | dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n", | 3133 | dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n", |
2834 | io->io1.minimum, io->io1.maximum, | 3134 | io->io1.minimum, io->io1.maximum, |
2835 | io->io1.address_length); | 3135 | io->io1.address_length); |
@@ -2837,7 +3137,7 @@ static int sony_pic_add(struct acpi_device *device) | |||
2837 | if (io->io2.minimum) { | 3137 | if (io->io2.minimum) { |
2838 | if (request_region(io->io2.minimum, | 3138 | if (request_region(io->io2.minimum, |
2839 | io->io2.address_length, | 3139 | io->io2.address_length, |
2840 | "Sony Programable I/O Device")) { | 3140 | "Sony Programmable I/O Device")) { |
2841 | dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n", | 3141 | dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n", |
2842 | io->io2.minimum, io->io2.maximum, | 3142 | io->io2.minimum, io->io2.maximum, |
2843 | io->io2.address_length); | 3143 | io->io2.address_length); |
@@ -2860,7 +3160,7 @@ static int sony_pic_add(struct acpi_device *device) | |||
2860 | } | 3160 | } |
2861 | } | 3161 | } |
2862 | if (!spic_dev.cur_ioport) { | 3162 | if (!spic_dev.cur_ioport) { |
2863 | printk(KERN_ERR DRV_PFX "Failed to request_region.\n"); | 3163 | pr_err(DRV_PFX "Failed to request_region.\n"); |
2864 | result = -ENODEV; | 3164 | result = -ENODEV; |
2865 | goto err_remove_compat; | 3165 | goto err_remove_compat; |
2866 | } | 3166 | } |
@@ -2880,7 +3180,7 @@ static int sony_pic_add(struct acpi_device *device) | |||
2880 | } | 3180 | } |
2881 | } | 3181 | } |
2882 | if (!spic_dev.cur_irq) { | 3182 | if (!spic_dev.cur_irq) { |
2883 | printk(KERN_ERR DRV_PFX "Failed to request_irq.\n"); | 3183 | pr_err(DRV_PFX "Failed to request_irq.\n"); |
2884 | result = -ENODEV; | 3184 | result = -ENODEV; |
2885 | goto err_release_region; | 3185 | goto err_release_region; |
2886 | } | 3186 | } |
@@ -2888,7 +3188,7 @@ static int sony_pic_add(struct acpi_device *device) | |||
2888 | /* set resource status _SRS */ | 3188 | /* set resource status _SRS */ |
2889 | result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); | 3189 | result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq); |
2890 | if (result) { | 3190 | if (result) { |
2891 | printk(KERN_ERR DRV_PFX "Couldn't enable device.\n"); | 3191 | pr_err(DRV_PFX "Couldn't enable device.\n"); |
2892 | goto err_free_irq; | 3192 | goto err_free_irq; |
2893 | } | 3193 | } |
2894 | 3194 | ||
@@ -2997,8 +3297,7 @@ static int __init sony_laptop_init(void) | |||
2997 | if (!no_spic && dmi_check_system(sonypi_dmi_table)) { | 3297 | if (!no_spic && dmi_check_system(sonypi_dmi_table)) { |
2998 | result = acpi_bus_register_driver(&sony_pic_driver); | 3298 | result = acpi_bus_register_driver(&sony_pic_driver); |
2999 | if (result) { | 3299 | if (result) { |
3000 | printk(KERN_ERR DRV_PFX | 3300 | pr_err(DRV_PFX "Unable to register SPIC driver."); |
3001 | "Unable to register SPIC driver."); | ||
3002 | goto out; | 3301 | goto out; |
3003 | } | 3302 | } |
3004 | spic_drv_registered = 1; | 3303 | spic_drv_registered = 1; |
@@ -3006,7 +3305,7 @@ static int __init sony_laptop_init(void) | |||
3006 | 3305 | ||
3007 | result = acpi_bus_register_driver(&sony_nc_driver); | 3306 | result = acpi_bus_register_driver(&sony_nc_driver); |
3008 | if (result) { | 3307 | if (result) { |
3009 | printk(KERN_ERR DRV_PFX "Unable to register SNC driver."); | 3308 | pr_err(DRV_PFX "Unable to register SNC driver."); |
3010 | goto out_unregister_pic; | 3309 | goto out_unregister_pic; |
3011 | } | 3310 | } |
3012 | 3311 | ||
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 947bdcaa0ce9..a08561f5349e 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c | |||
@@ -2407,7 +2407,7 @@ static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn, | |||
2407 | * This code is supposed to duplicate the IBM firmware behaviour: | 2407 | * This code is supposed to duplicate the IBM firmware behaviour: |
2408 | * - Pressing MUTE issues mute hotkey message, even when already mute | 2408 | * - Pressing MUTE issues mute hotkey message, even when already mute |
2409 | * - Pressing Volume up/down issues volume up/down hotkey messages, | 2409 | * - Pressing Volume up/down issues volume up/down hotkey messages, |
2410 | * even when already at maximum or minumum volume | 2410 | * even when already at maximum or minimum volume |
2411 | * - The act of unmuting issues volume up/down notification, | 2411 | * - The act of unmuting issues volume up/down notification, |
2412 | * depending which key was used to unmute | 2412 | * depending which key was used to unmute |
2413 | * | 2413 | * |
@@ -2990,7 +2990,7 @@ static void tpacpi_send_radiosw_update(void) | |||
2990 | * rfkill input events, or we will race the rfkill core input | 2990 | * rfkill input events, or we will race the rfkill core input |
2991 | * handler. | 2991 | * handler. |
2992 | * | 2992 | * |
2993 | * tpacpi_inputdev_send_mutex works as a syncronization point | 2993 | * tpacpi_inputdev_send_mutex works as a synchronization point |
2994 | * for the above. | 2994 | * for the above. |
2995 | * | 2995 | * |
2996 | * We optimize to avoid numerous calls to hotkey_get_wlsw. | 2996 | * We optimize to avoid numerous calls to hotkey_get_wlsw. |
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c new file mode 100644 index 000000000000..c1372ed9d2e9 --- /dev/null +++ b/drivers/platform/x86/xo15-ebook.c | |||
@@ -0,0 +1,180 @@ | |||
1 | /* | ||
2 | * OLPC XO-1.5 ebook switch driver | ||
3 | * (based on generic ACPI button driver) | ||
4 | * | ||
5 | * Copyright (C) 2009 Paul Fox <pgf@laptop.org> | ||
6 | * Copyright (C) 2010 One Laptop per Child | ||
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 (at | ||
11 | * your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/types.h> | ||
18 | #include <linux/input.h> | ||
19 | #include <acpi/acpi_bus.h> | ||
20 | #include <acpi/acpi_drivers.h> | ||
21 | |||
22 | #define MODULE_NAME "xo15-ebook" | ||
23 | #define PREFIX MODULE_NAME ": " | ||
24 | |||
25 | #define XO15_EBOOK_CLASS MODULE_NAME | ||
26 | #define XO15_EBOOK_TYPE_UNKNOWN 0x00 | ||
27 | #define XO15_EBOOK_NOTIFY_STATUS 0x80 | ||
28 | |||
29 | #define XO15_EBOOK_SUBCLASS "ebook" | ||
30 | #define XO15_EBOOK_HID "XO15EBK" | ||
31 | #define XO15_EBOOK_DEVICE_NAME "EBook Switch" | ||
32 | |||
33 | ACPI_MODULE_NAME(MODULE_NAME); | ||
34 | |||
35 | MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); | ||
36 | MODULE_LICENSE("GPL"); | ||
37 | |||
38 | static const struct acpi_device_id ebook_device_ids[] = { | ||
39 | { XO15_EBOOK_HID, 0 }, | ||
40 | { "", 0 }, | ||
41 | }; | ||
42 | MODULE_DEVICE_TABLE(acpi, ebook_device_ids); | ||
43 | |||
44 | struct ebook_switch { | ||
45 | struct input_dev *input; | ||
46 | char phys[32]; /* for input device */ | ||
47 | }; | ||
48 | |||
49 | static int ebook_send_state(struct acpi_device *device) | ||
50 | { | ||
51 | struct ebook_switch *button = acpi_driver_data(device); | ||
52 | unsigned long long state; | ||
53 | acpi_status status; | ||
54 | |||
55 | status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state); | ||
56 | if (ACPI_FAILURE(status)) | ||
57 | return -EIO; | ||
58 | |||
59 | /* input layer checks if event is redundant */ | ||
60 | input_report_switch(button->input, SW_TABLET_MODE, !state); | ||
61 | input_sync(button->input); | ||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | static void ebook_switch_notify(struct acpi_device *device, u32 event) | ||
66 | { | ||
67 | switch (event) { | ||
68 | case ACPI_FIXED_HARDWARE_EVENT: | ||
69 | case XO15_EBOOK_NOTIFY_STATUS: | ||
70 | ebook_send_state(device); | ||
71 | break; | ||
72 | default: | ||
73 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | ||
74 | "Unsupported event [0x%x]\n", event)); | ||
75 | break; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | static int ebook_switch_resume(struct acpi_device *device) | ||
80 | { | ||
81 | return ebook_send_state(device); | ||
82 | } | ||
83 | |||
84 | static int ebook_switch_add(struct acpi_device *device) | ||
85 | { | ||
86 | struct ebook_switch *button; | ||
87 | struct input_dev *input; | ||
88 | const char *hid = acpi_device_hid(device); | ||
89 | char *name, *class; | ||
90 | int error; | ||
91 | |||
92 | button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL); | ||
93 | if (!button) | ||
94 | return -ENOMEM; | ||
95 | |||
96 | device->driver_data = button; | ||
97 | |||
98 | button->input = input = input_allocate_device(); | ||
99 | if (!input) { | ||
100 | error = -ENOMEM; | ||
101 | goto err_free_button; | ||
102 | } | ||
103 | |||
104 | name = acpi_device_name(device); | ||
105 | class = acpi_device_class(device); | ||
106 | |||
107 | if (strcmp(hid, XO15_EBOOK_HID)) { | ||
108 | printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid); | ||
109 | error = -ENODEV; | ||
110 | goto err_free_input; | ||
111 | } | ||
112 | |||
113 | strcpy(name, XO15_EBOOK_DEVICE_NAME); | ||
114 | sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS); | ||
115 | |||
116 | snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid); | ||
117 | |||
118 | input->name = name; | ||
119 | input->phys = button->phys; | ||
120 | input->id.bustype = BUS_HOST; | ||
121 | input->dev.parent = &device->dev; | ||
122 | |||
123 | input->evbit[0] = BIT_MASK(EV_SW); | ||
124 | set_bit(SW_TABLET_MODE, input->swbit); | ||
125 | |||
126 | error = input_register_device(input); | ||
127 | if (error) | ||
128 | goto err_free_input; | ||
129 | |||
130 | ebook_send_state(device); | ||
131 | |||
132 | if (device->wakeup.flags.valid) { | ||
133 | /* Button's GPE is run-wake GPE */ | ||
134 | acpi_enable_gpe(device->wakeup.gpe_device, | ||
135 | device->wakeup.gpe_number); | ||
136 | device_set_wakeup_enable(&device->dev, true); | ||
137 | } | ||
138 | |||
139 | return 0; | ||
140 | |||
141 | err_free_input: | ||
142 | input_free_device(input); | ||
143 | err_free_button: | ||
144 | kfree(button); | ||
145 | return error; | ||
146 | } | ||
147 | |||
148 | static int ebook_switch_remove(struct acpi_device *device, int type) | ||
149 | { | ||
150 | struct ebook_switch *button = acpi_driver_data(device); | ||
151 | |||
152 | input_unregister_device(button->input); | ||
153 | kfree(button); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | static struct acpi_driver xo15_ebook_driver = { | ||
158 | .name = MODULE_NAME, | ||
159 | .class = XO15_EBOOK_CLASS, | ||
160 | .ids = ebook_device_ids, | ||
161 | .ops = { | ||
162 | .add = ebook_switch_add, | ||
163 | .resume = ebook_switch_resume, | ||
164 | .remove = ebook_switch_remove, | ||
165 | .notify = ebook_switch_notify, | ||
166 | }, | ||
167 | }; | ||
168 | |||
169 | static int __init xo15_ebook_init(void) | ||
170 | { | ||
171 | return acpi_bus_register_driver(&xo15_ebook_driver); | ||
172 | } | ||
173 | |||
174 | static void __exit xo15_ebook_exit(void) | ||
175 | { | ||
176 | acpi_bus_unregister_driver(&xo15_ebook_driver); | ||
177 | } | ||
178 | |||
179 | module_init(xo15_ebook_init); | ||
180 | module_exit(xo15_ebook_exit); | ||