diff options
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/Kconfig | 29 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 2 | ||||
-rw-r--r-- | drivers/platform/x86/acerhdf.c | 63 | ||||
-rw-r--r-- | drivers/platform/x86/asus-laptop.c | 25 | ||||
-rw-r--r-- | drivers/platform/x86/asus_acpi.c | 19 | ||||
-rw-r--r-- | drivers/platform/x86/dell-laptop.c | 86 | ||||
-rw-r--r-- | drivers/platform/x86/dell-wmi.c | 129 | ||||
-rw-r--r-- | drivers/platform/x86/eeepc-laptop.c | 1415 | ||||
-rw-r--r-- | drivers/platform/x86/hp-wmi.c | 139 | ||||
-rw-r--r-- | drivers/platform/x86/msi-wmi.c | 293 | ||||
-rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 1178 | ||||
-rw-r--r-- | drivers/platform/x86/toshiba_bluetooth.c | 144 | ||||
-rw-r--r-- | drivers/platform/x86/wmi.c | 175 |
13 files changed, 2643 insertions, 1054 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 55ca39dea42e..fc5bf9d2a3f3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -334,6 +334,8 @@ config EEEPC_LAPTOP depends on HOTPLUG_PCI select BACKLIGHT_CLASS_DEVICE select HWMON + select LEDS_CLASS + select NEW_LEDS ---help--- This driver supports the Fn-Fx keys on Eee PC laptops. @@ -365,6 +367,18 @@ config ACPI_WMI It is safe to enable this driver even if your DSDT doesn't define any ACPI-WMI devices. +config MSI_WMI + tristate "MSI WMI extras" + depends on ACPI_WMI + depends on INPUT + depends on BACKLIGHT_CLASS_DEVICE + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on MSI laptops. + + To compile this driver as a module, choose M here: the module will + be called msi-wmi. + config ACPI_ASUS tristate "ASUS/Medion Laptop Extras (DEPRECATED)" depends on ACPI @@ -435,4 +449,19 @@ config ACPI_TOSHIBA If you have a legacy free Toshiba laptop (such as the Libretto L1 series), say Y. + +config TOSHIBA_BT_RFKILL + tristate "Toshiba Bluetooth RFKill switch support" + depends on ACPI + ---help--- + This driver adds support for Bluetooth events for the RFKill + switch on modern Toshiba laptops with full ACPI support and + an RFKill switch. + + This driver handles RFKill events for the TOS6205 Bluetooth, + and re-enables it when the switch is set back to the 'on' + position. + + If you have a modern Toshiba laptop with a Bluetooth and an + RFKill switch (such as the Portege R500), say Y. endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index d1c16210a512..b7474b6a8bf1 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -18,6 +18,8 @@ obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ACPI_WMI) += wmi.o +obj-$(CONFIG_MSI_WMI) += msi-wmi.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o +obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index be27aa47e810..79b15b9d9cf0 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -52,7 +52,7 @@ */ #undef START_IN_KERNEL_MODE -#define DRV_VER "0.5.18" +#define DRV_VER "0.5.20" /* * According to the Atom N270 datasheet, @@ -112,12 +112,14 @@ module_param_string(force_product, force_product, 16, 0); MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check"); /* - * cmd_off: to switch the fan completely off / to check if the fan is off + * cmd_off: to switch the fan completely off + * chk_off: to check if the fan is off * cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then * the fan speed depending on the temperature */ struct fancmd { u8 cmd_off; + u8 chk_off; u8 cmd_auto; }; @@ -134,32 +136,41 @@ struct bios_settings_t { /* Register addresses and values for different BIOS versions */ static const struct bios_settings_t bios_tbl[] = { /* AOA110 */ - {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, - {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, - {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x21, 0x00} }, /* AOA150 */ - {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} }, - {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x20, 0x00} }, + /* Acer 1410 */ + {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, /* special BIOS / other */ - {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, - {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00} }, - {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00} }, - {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, - {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, + {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Gateway ", "LT31 ", "v1.3103 ", 0x55, 0x58, + {0x10, 0x0f, 0x00} }, + {"Gateway ", "LT31 ", "v1.3201 ", 0x55, 0x58, + {0x10, 0x0f, 0x00} }, + {"Gateway ", "LT31 ", "v1.3302 ", 0x55, 0x58, + {0x10, 0x0f, 0x00} }, + {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, /* pewpew-terminator */ - {"", "", "", 0, 0, {0, 0} } + {"", "", "", 0, 0, {0, 0, 0} } }; static const struct bios_settings_t *bios_cfg __read_mostly; @@ -183,7 +194,7 @@ static int acerhdf_get_fanstate(int *state) if (ec_read(bios_cfg->fanreg, &fan)) return -EINVAL; - if (fan != bios_cfg->cmd.cmd_off) + if (fan != bios_cfg->cmd.chk_off) *state = ACERHDF_FAN_AUTO; else *state = ACERHDF_FAN_OFF; diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index b39d2bb3e75b..61a1c7503658 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -221,6 +221,7 @@ static struct asus_hotk *hotk; */ static const struct acpi_device_id asus_device_ids[] = { {"ATK0100", 0}, + {"ATK0101", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, asus_device_ids); @@ -232,6 +233,7 @@ static void asus_hotk_notify(struct acpi_device *device, u32 event); static struct acpi_driver asus_hotk_driver = { .name = ASUS_HOTK_NAME, .class = ASUS_HOTK_CLASS, + .owner = THIS_MODULE, .ids = asus_device_ids, .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, .ops = { @@ -293,6 +295,11 @@ struct key_entry { enum { KE_KEY, KE_END }; static struct key_entry asus_keymap[] = { + {KE_KEY, 0x02, KEY_SCREENLOCK}, + {KE_KEY, 0x05, KEY_WLAN}, + {KE_KEY, 0x08, KEY_F13}, + {KE_KEY, 0x17, KEY_ZOOM}, + {KE_KEY, 0x1f, KEY_BATTERY}, {KE_KEY, 0x30, KEY_VOLUMEUP}, {KE_KEY, 0x31, KEY_VOLUMEDOWN}, {KE_KEY, 0x32, KEY_MUTE}, @@ -312,8 +319,11 @@ static struct key_entry asus_keymap[] = { {KE_KEY, 0x5F, KEY_WLAN}, {KE_KEY, 0x60, KEY_SWITCHVIDEOMODE}, {KE_KEY, 0x61, KEY_SWITCHVIDEOMODE}, - {KE_KEY, 0x6B, BTN_TOUCH}, /* Lock Mouse */ + {KE_KEY, 0x62, KEY_SWITCHVIDEOMODE}, + {KE_KEY, 0x63, KEY_SWITCHVIDEOMODE}, + {KE_KEY, 0x6B, KEY_F13}, /* Lock Touchpad */ {KE_KEY, 0x82, KEY_CAMERA}, + {KE_KEY, 0x88, KEY_WLAN }, {KE_KEY, 0x8A, KEY_PROG1}, {KE_KEY, 0x95, KEY_MEDIA}, {KE_KEY, 0x99, KEY_PHONE}, @@ -1240,9 +1250,6 @@ static int asus_hotk_add(struct acpi_device *device) { int result; - if (!device) - return -EINVAL; - pr_notice("Asus Laptop Support version %s\n", ASUS_LAPTOP_VERSION); @@ -1283,8 +1290,8 @@ static int asus_hotk_add(struct acpi_device *device) hotk->ledd_status = 0xFFF; /* Set initial values of light sensor and level */ - hotk->light_switch = 1; /* Default to light sensor disabled */ - hotk->light_level = 0; /* level 5 for sensor sensitivity */ + hotk->light_switch = 0; /* Default to light sensor disabled */ + hotk->light_level = 5; /* level 5 for sensor sensitivity */ if (ls_switch_handle) set_light_sens_switch(hotk->light_switch); @@ -1306,9 +1313,6 @@ end: static int asus_hotk_remove(struct acpi_device *device, int type) { - if (!device || !acpi_driver_data(device)) - return -EINVAL; - kfree(hotk->name); kfree(hotk); @@ -1444,9 +1448,6 @@ static int __init asus_laptop_init(void) { int result; - if (acpi_disabled) - return -ENODEV; - result = acpi_bus_register_driver(&asus_hotk_driver); if (result < 0) return result; diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c index ddf5240ade8c..0c9c53111a22 100644 --- a/drivers/platform/x86/asus_acpi.c +++ b/drivers/platform/x86/asus_acpi.c @@ -466,6 +466,7 @@ MODULE_DEVICE_TABLE(acpi, asus_device_ids); static struct acpi_driver asus_hotk_driver = { .name = "asus_acpi", .class = ACPI_HOTK_CLASS, + .owner = THIS_MODULE, .ids = asus_device_ids, .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, .ops = { @@ -1334,9 +1335,6 @@ static int asus_hotk_add(struct acpi_device *device) acpi_status status = AE_OK; int result; - if (!device) - return -EINVAL; - printk(KERN_NOTICE "Asus Laptop ACPI Extras version %s\n", ASUS_ACPI_VERSION); @@ -1392,9 +1390,6 @@ end: static int asus_hotk_remove(struct acpi_device *device, int type) { - if (!device || !acpi_driver_data(device)) - return -EINVAL; - asus_hotk_remove_fs(device); kfree(hotk); @@ -1422,21 +1417,17 @@ static int __init asus_acpi_init(void) { int result; - if (acpi_disabled) - return -ENODEV; + result = acpi_bus_register_driver(&asus_hotk_driver); + if (result < 0) + return result; asus_proc_dir = proc_mkdir(PROC_ASUS, acpi_root_dir); if (!asus_proc_dir) { printk(KERN_ERR "Asus ACPI: Unable to create /proc entry\n"); + acpi_bus_unregister_driver(&asus_hotk_driver); return -ENODEV; } - result = acpi_bus_register_driver(&asus_hotk_driver); - if (result < 0) { - remove_proc_entry(PROC_ASUS, acpi_root_dir); - return result; - } - /* * This is a bit of a kludge. We only want this module loaded * for ASUS systems, but there's currently no way to probe the diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 74909c4aaeea..3780994dc8f2 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -58,6 +58,14 @@ static int da_command_code; static int da_num_tokens; static struct calling_interface_token *da_tokens; +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-laptop", + .owner = THIS_MODULE, + } +}; + +static struct platform_device *platform_device; static struct backlight_device *dell_backlight_device; static struct rfkill *wifi_rfkill; static struct rfkill *bluetooth_rfkill; @@ -74,7 +82,7 @@ static const struct dmi_system_id __initdata dell_device_table[] = { { } }; -static void parse_da_table(const struct dmi_header *dm) +static void __init parse_da_table(const struct dmi_header *dm) { /* Final token is a terminator, so we don't want to copy it */ int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; @@ -103,7 +111,7 @@ static void parse_da_table(const struct dmi_header *dm) da_num_tokens += tokens; } -static void find_tokens(const struct dmi_header *dm, void *dummy) +static void __init find_tokens(const struct dmi_header *dm, void *dummy) { switch (dm->type) { case 0xd4: /* Indexed IO */ @@ -197,8 +205,8 @@ static void dell_rfkill_query(struct rfkill *rfkill, void *data) dell_send_request(&buffer, 17, 11); status = buffer.output[1]; - if (status & BIT(bit)) - rfkill_set_hw_state(rfkill, !!(status & BIT(16))); + rfkill_set_sw_state(rfkill, !!(status & BIT(bit))); + rfkill_set_hw_state(rfkill, !(status & BIT(16))); } static const struct rfkill_ops dell_rfkill_ops = { @@ -206,7 +214,7 @@ static const struct rfkill_ops dell_rfkill_ops = { .query = dell_rfkill_query, }; -static int dell_setup_rfkill(void) +static int __init dell_setup_rfkill(void) { struct calling_interface_buffer buffer; int status; @@ -217,7 +225,8 @@ static int dell_setup_rfkill(void) status = buffer.output[1]; if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { - wifi_rfkill = rfkill_alloc("dell-wifi", NULL, RFKILL_TYPE_WLAN, + wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, + RFKILL_TYPE_WLAN, &dell_rfkill_ops, (void *) 1); if (!wifi_rfkill) { ret = -ENOMEM; @@ -229,7 +238,8 @@ static int dell_setup_rfkill(void) } if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { - bluetooth_rfkill = rfkill_alloc("dell-bluetooth", NULL, + bluetooth_rfkill = rfkill_alloc("dell-bluetooth", + &platform_device->dev, RFKILL_TYPE_BLUETOOTH, &dell_rfkill_ops, (void *) 2); if (!bluetooth_rfkill) { @@ -242,7 +252,9 @@ static int dell_setup_rfkill(void) } if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { - wwan_rfkill = rfkill_alloc("dell-wwan", NULL, RFKILL_TYPE_WWAN, + wwan_rfkill = rfkill_alloc("dell-wwan", + &platform_device->dev, + RFKILL_TYPE_WWAN, &dell_rfkill_ops, (void *) 3); if (!wwan_rfkill) { ret = -ENOMEM; @@ -268,6 +280,22 @@ err_wifi: return ret; } +static void dell_cleanup_rfkill(void) +{ + if (wifi_rfkill) { + rfkill_unregister(wifi_rfkill); + rfkill_destroy(wifi_rfkill); + } + if (bluetooth_rfkill) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } + if (wwan_rfkill) { + rfkill_unregister(wwan_rfkill); + rfkill_destroy(wwan_rfkill); + } +} + static int dell_send_intensity(struct backlight_device *bd) { struct calling_interface_buffer buffer; @@ -326,11 +354,23 @@ static int __init dell_init(void) return -ENODEV; } + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("dell-laptop", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + ret = dell_setup_rfkill(); if (ret) { printk(KERN_WARNING "dell-laptop: Unable to setup rfkill\n"); - goto out; + goto fail_rfkill; } #ifdef CONFIG_ACPI @@ -352,13 +392,13 @@ static int __init dell_init(void) if (max_intensity) { dell_backlight_device = backlight_device_register( "dell_backlight", - NULL, NULL, + &platform_device->dev, NULL, &dell_ops); if (IS_ERR(dell_backlight_device)) { ret = PTR_ERR(dell_backlight_device); dell_backlight_device = NULL; - goto out; + goto fail_backlight; } dell_backlight_device->props.max_brightness = max_intensity; @@ -368,13 +408,16 @@ static int __init dell_init(void) } return 0; -out: - if (wifi_rfkill) - rfkill_unregister(wifi_rfkill); - if (bluetooth_rfkill) - rfkill_unregister(bluetooth_rfkill); - if (wwan_rfkill) - rfkill_unregister(wwan_rfkill); + +fail_backlight: + dell_cleanup_rfkill(); +fail_rfkill: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: kfree(da_tokens); return ret; } @@ -382,12 +425,7 @@ out: static void __exit dell_exit(void) { backlight_device_unregister(dell_backlight_device); - if (wifi_rfkill) - rfkill_unregister(wifi_rfkill); - if (bluetooth_rfkill) - rfkill_unregister(bluetooth_rfkill); - if (wwan_rfkill) - rfkill_unregister(wwan_rfkill); + dell_cleanup_rfkill(); } module_init(dell_init); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 0f900cc9fa7a..67f3fe71c509 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -31,6 +31,7 @@ #include <acpi/acpi_drivers.h> #include <linux/acpi.h> #include <linux/string.h> +#include <linux/dmi.h> MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); @@ -38,6 +39,8 @@ MODULE_LICENSE("GPL"); #define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" +static int acpi_video; + MODULE_ALIAS("wmi:"DELL_EVENT_GUID); struct key_entry { @@ -54,7 +57,7 @@ enum { KE_KEY, KE_SW, KE_IGNORE, KE_END }; * via the keyboard controller so should not be sent again. */ -static struct key_entry dell_wmi_keymap[] = { +static struct key_entry dell_legacy_wmi_keymap[] = { {KE_KEY, 0xe045, KEY_PROG1}, {KE_KEY, 0xe009, KEY_EJECTCD}, @@ -72,7 +75,7 @@ static struct key_entry dell_wmi_keymap[] = { /* The next device is at offset 6, the active devices are at offset 8 and the attached devices at offset 10 */ - {KE_KEY, 0xe00b, KEY_DISPLAYTOGGLE}, + {KE_KEY, 0xe00b, KEY_SWITCHVIDEOMODE}, {KE_IGNORE, 0xe00c, KEY_KBDILLUMTOGGLE}, @@ -96,6 +99,47 @@ static struct key_entry dell_wmi_keymap[] = { {KE_END, 0} }; +static bool dell_new_hk_type; + +struct dell_new_keymap_entry { + u16 scancode; + u16 keycode; +}; + +struct dell_hotkey_table { + struct dmi_header header; + struct dell_new_keymap_entry keymap[]; + +}; + +static struct key_entry *dell_new_wmi_keymap; + +static u16 bios_to_linux_keycode[256] = { + + KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, + KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE, + KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD, + KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN, + KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE, + KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2, + KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + KEY_PROG3 +}; + + +static struct key_entry *dell_wmi_keymap = dell_legacy_wmi_keymap; + static struct input_dev *dell_wmi_input_dev; static struct key_entry *dell_wmi_get_entry_by_scancode(int code) @@ -164,24 +208,78 @@ static void dell_wmi_notify(u32 value, void *context) obj = (union acpi_object *)response.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER) { - int *buffer = (int *)obj->buffer.pointer; - /* - * The upper bytes of the event may contain - * additional information, so mask them off for the - * scancode lookup - */ - key = dell_wmi_get_entry_by_scancode(buffer[1] & 0xFFFF); - if (key) { + int reported_key; + u16 *buffer_entry = (u16 *)obj->buffer.pointer; + if (dell_new_hk_type && (buffer_entry[1] != 0x10)) { + printk(KERN_INFO "dell-wmi: Received unknown WMI event" + " (0x%x)\n", buffer_entry[1]); + return; + } + + if (dell_new_hk_type) + reported_key = (int)buffer_entry[2]; + else + reported_key = (int)buffer_entry[1] & 0xffff; + + key = dell_wmi_get_entry_by_scancode(reported_key); + + if (!key) { + printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", + reported_key); + } else if ((key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) { + /* Don't report brightness notifications that will also + * come via ACPI */ + return; + } else { input_report_key(dell_wmi_input_dev, key->keycode, 1); input_sync(dell_wmi_input_dev); input_report_key(dell_wmi_input_dev, key->keycode, 0); input_sync(dell_wmi_input_dev); - } else if (buffer[1] & 0xFFFF) - printk(KERN_INFO "dell-wmi: Unknown key %x pressed\n", - buffer[1] & 0xFFFF); + } } } + +static void setup_new_hk_map(const struct dmi_header *dm) +{ + + int i; + int hotkey_num = (dm->length-4)/sizeof(struct dell_new_keymap_entry); + struct dell_hotkey_table *table = + container_of(dm, struct dell_hotkey_table, header); + + dell_new_wmi_keymap = kzalloc((hotkey_num+1) * + sizeof(struct key_entry), GFP_KERNEL); + + for (i = 0; i < hotkey_num; i++) { + dell_new_wmi_keymap[i].type = KE_KEY; + dell_new_wmi_keymap[i].code = table->keymap[i].scancode; + dell_new_wmi_keymap[i].keycode = + (table->keymap[i].keycode > 255) ? 0 : + bios_to_linux_keycode[table->keymap[i].keycode]; + } + + dell_new_wmi_keymap[i].type = KE_END; + dell_new_wmi_keymap[i].code = 0; + dell_new_wmi_keymap[i].keycode = 0; + + dell_wmi_keymap = dell_new_wmi_keymap; + +} + + +static void find_hk_type(const struct dmi_header *dm, void *dummy) +{ + + if ((dm->type == 0xb2) && (dm->length > 6)) { + dell_new_hk_type = true; + setup_new_hk_map(dm); + } + +} + + static int __init dell_wmi_input_setup(void) { struct key_entry *key; @@ -226,6 +324,9 @@ static int __init dell_wmi_init(void) int err; if (wmi_has_guid(DELL_EVENT_GUID)) { + + dmi_walk(find_hk_type, NULL); + err = dell_wmi_input_setup(); if (err) @@ -240,6 +341,8 @@ static int __init dell_wmi_init(void) return err; } + acpi_video = acpi_video_backlight_support(); + } else printk(KERN_WARNING "dell-wmi: No known WMI GUID found\n"); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index e647a856b9bf..5838c69b2fb3 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -1,5 +1,5 @@ /* - * eepc-laptop.c - Asus Eee PC extras + * eeepc-laptop.c - Asus Eee PC extras * * Based on asus_acpi.c as patched for the Eee PC by Asus: * ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar @@ -34,20 +34,23 @@ #include <linux/rfkill.h> #include <linux/pci.h> #include <linux/pci_hotplug.h> +#include <linux/leds.h> #define EEEPC_LAPTOP_VERSION "0.1" +#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" +#define EEEPC_LAPTOP_FILE "eeepc" -#define EEEPC_HOTK_NAME "Eee PC Hotkey Driver" -#define EEEPC_HOTK_FILE "eeepc" -#define EEEPC_HOTK_CLASS "hotkey" -#define EEEPC_HOTK_DEVICE_NAME "Hotkey" -#define EEEPC_HOTK_HID "ASUS010" +#define EEEPC_ACPI_CLASS "hotkey" +#define EEEPC_ACPI_DEVICE_NAME "Hotkey" +#define EEEPC_ACPI_HID "ASUS010" +MODULE_AUTHOR("Corentin Chary, Eric Cooper"); +MODULE_DESCRIPTION(EEEPC_LAPTOP_NAME); +MODULE_LICENSE("GPL"); /* * Definitions for Asus EeePC */ -#define NOTIFY_WLAN_ON 0x10 #define NOTIFY_BRN_MIN 0x20 #define NOTIFY_BRN_MAX 0x2f @@ -117,58 +120,6 @@ static const char *cm_setv[] = { NULL, NULL, "PBPS", "TPDS" }; -#define EEEPC_EC "\\_SB.PCI0.SBRG.EC0." - -#define EEEPC_EC_FAN_PWM EEEPC_EC "SC02" /* Fan PWM duty cycle (%) */ -#define EEEPC_EC_SC02 0x63 -#define EEEPC_EC_FAN_HRPM EEEPC_EC "SC05" /* High byte, fan speed (RPM) */ -#define EEEPC_EC_FAN_LRPM EEEPC_EC "SC06" /* Low byte, fan speed (RPM) */ -#define EEEPC_EC_FAN_CTRL EEEPC_EC "SFB3" /* Byte containing SF25 */ -#define EEEPC_EC_SFB3 0xD3 - -/* - * This is the main structure, we can use it to store useful information - * about the hotk device - */ -struct eeepc_hotk { - struct acpi_device *device; /* the device we are in */ - acpi_handle handle; /* the handle of the hotk device */ - u32 cm_supported; /* the control methods supported - by this BIOS */ - uint init_flag; /* Init flags */ - u16 event_count[128]; /* count for each event */ - struct input_dev *inputdev; - u16 *keycode_map; - struct rfkill *wlan_rfkill; - struct rfkill *bluetooth_rfkill; - struct rfkill *wwan3g_rfkill; - struct rfkill *wimax_rfkill; - struct hotplug_slot *hotplug_slot; - struct mutex hotplug_lock; -}; - -/* The actual device the driver binds to */ -static struct eeepc_hotk *ehotk; - -/* Platform device/driver */ -static int eeepc_hotk_thaw(struct device *device); -static int eeepc_hotk_restore(struct device *device); - -static const struct dev_pm_ops eeepc_pm_ops = { - .thaw = eeepc_hotk_thaw, - .restore = eeepc_hotk_restore, -}; - -static struct platform_driver platform_driver = { - .driver = { - .name = EEEPC_HOTK_FILE, - .owner = THIS_MODULE, - .pm = &eeepc_pm_ops, - } -}; - -static struct platform_device *platform_device; - struct key_entry { char type; u8 code; @@ -177,7 +128,7 @@ struct key_entry { enum { KE_KEY, KE_END }; -static struct key_entry eeepc_keymap[] = { +static const struct key_entry eeepc_keymap[] = { /* Sleep already handled via generic ACPI code */ {KE_KEY, 0x10, KEY_WLAN }, {KE_KEY, 0x11, KEY_WLAN }, @@ -185,77 +136,56 @@ static struct key_entry eeepc_keymap[] = { {KE_KEY, 0x13, KEY_MUTE }, {KE_KEY, 0x14, KEY_VOLUMEDOWN }, {KE_KEY, 0x15, KEY_VOLUMEUP }, + {KE_KEY, 0x16, KEY_DISPLAY_OFF }, {KE_KEY, 0x1a, KEY_COFFEE }, {KE_KEY, 0x1b, KEY_ZOOM }, {KE_KEY, 0x1c, KEY_PROG2 }, {KE_KEY, 0x1d, KEY_PROG3 }, - {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN }, - {KE_KEY, NOTIFY_BRN_MIN + 2, KEY_BRIGHTNESSUP }, + {KE_KEY, NOTIFY_BRN_MIN, KEY_BRIGHTNESSDOWN }, + {KE_KEY, NOTIFY_BRN_MAX, KEY_BRIGHTNESSUP }, {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE }, {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE }, {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE }, + {KE_KEY, 0x37, KEY_F13 }, /* Disable Touchpad */ + {KE_KEY, 0x38, KEY_F14 }, {KE_END, 0}, }; + /* - * The hotkey driver declaration + * This is the main structure, we can use it to store useful information */ -static int eeepc_hotk_add(struct acpi_device *device); -static int eeepc_hotk_remove(struct acpi_device *device, int type); -static void eeepc_hotk_notify(struct acpi_device *device, u32 event); - -static const struct acpi_device_id eeepc_device_ids[] = { - {EEEPC_HOTK_HID, 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, eeepc_device_ids); - -static struct acpi_driver eeepc_hotk_driver = { - .name = EEEPC_HOTK_NAME, - .class = EEEPC_HOTK_CLASS, - .ids = eeepc_device_ids, - .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, - .ops = { - .add = eeepc_hotk_add, - .remove = eeepc_hotk_remove, - .notify = eeepc_hotk_notify, - }, -}; +struct eeepc_laptop { + acpi_handle handle; /* the handle of the acpi device */ + u32 cm_supported; /* the control methods supported + by this BIOS */ + u16 event_count[128]; /* count for each event */ -/* PCI hotplug ops */ -static int eeepc_get_adapter_status(struct hotplug_slot *slot, u8 *value); + struct platform_device *platform_device; + struct device *hwmon_device; + struct backlight_device *backlight_device; -static struct hotplug_slot_ops eeepc_hotplug_slot_ops = { - .owner = THIS_MODULE, - .get_adapter_status = eeepc_get_adapter_status, - .get_power_status = eeepc_get_adapter_status, -}; + struct input_dev *inputdev; + struct key_entry *keymap; -/* The backlight device /sys/class/backlight */ -static struct backlight_device *eeepc_backlight_device; + struct rfkill *wlan_rfkill; + struct rfkill *bluetooth_rfkill; + struct rfkill *wwan3g_rfkill; + struct rfkill *wimax_rfkill; -/* The hwmon device */ -static struct device *eeepc_hwmon_device; + struct hotplug_slot *hotplug_slot; + struct mutex hotplug_lock; -/* - * The backlight class declaration - */ -static int read_brightness(struct backlight_device *bd); -static int update_bl_status(struct backlight_device *bd); -static struct backlight_ops eeepcbl_ops = { - .get_brightness = read_brightness, - .update_status = update_bl_status, + struct led_classdev tpd_led; + int tpd_led_wk; + struct workqueue_struct *led_workqueue; + struct work_struct tpd_led_work; }; -MODULE_AUTHOR("Corentin Chary, Eric Cooper"); -MODULE_DESCRIPTION(EEEPC_HOTK_NAME); -MODULE_LICENSE("GPL"); - /* * ACPI Helpers */ -static int write_acpi_int(acpi_handle handle, const char *method, int val, - struct acpi_buffer *output) +static int write_acpi_int(acpi_handle handle, const char *method, int val) { struct acpi_object_list params; union acpi_object in_obj; @@ -266,7 +196,7 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, in_obj.type = ACPI_TYPE_INTEGER; in_obj.integer.value = val; - status = acpi_evaluate_object(handle, (char *)method, ¶ms, output); + status = acpi_evaluate_object(handle, (char *)method, ¶ms, NULL); return (status == AE_OK ? 0 : -1); } @@ -285,81 +215,56 @@ static int read_acpi_int(acpi_handle handle, const char *method, int *val) } } -static int set_acpi(int cm, int value) +static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value) { - if (ehotk->cm_supported & (0x1 << cm)) { - const char *method = cm_setv[cm]; - if (method == NULL) - return -ENODEV; - if (write_acpi_int(ehotk->handle, method, value, NULL)) - pr_warning("Error writing %s\n", method); - } - return 0; -} + const char *method = cm_setv[cm]; -static int get_acpi(int cm) -{ - int value = -ENODEV; - if ((ehotk->cm_supported & (0x1 << cm))) { - const char *method = cm_getv[cm]; - if (method == NULL) - return -ENODEV; - if (read_acpi_int(ehotk->handle, method, &value)) - pr_warning("Error reading %s\n", method); - } - return value; -} - -/* - * Backlight - */ -static int read_brightness(struct backlight_device *bd) -{ - return get_acpi(CM_ASL_PANELBRIGHT); -} + if (method == NULL) + return -ENODEV; + if ((eeepc->cm_supported & (0x1 << cm)) == 0) + return -ENODEV; -static int set_brightness(struct backlight_device *bd, int value) -{ - value = max(0, min(15, value)); - return set_acpi(CM_ASL_PANELBRIGHT, value); + if (write_acpi_int(eeepc->handle, method, value)) + pr_warning("Error writing %s\n", method); + return 0; } -static int update_bl_status(struct backlight_device *bd) +static int get_acpi(struct eeepc_laptop *eeepc, int cm) { - return set_brightness(bd, bd->props.brightness); -} + const char *method = cm_getv[cm]; + int value; -/* - * Rfkill helpers - */ + if (method == NULL) + return -ENODEV; + if ((eeepc->cm_supported & (0x1 << cm)) == 0) + return -ENODEV; -static bool eeepc_wlan_rfkill_blocked(void) -{ - if (get_acpi(CM_ASL_WLAN) == 1) - return false; - return true; + if (read_acpi_int(eeepc->handle, method, &value)) + pr_warning("Error reading %s\n", method); + return value; } -static int eeepc_rfkill_set(void *data, bool blocked) +static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm, + acpi_handle *handle) { - unsigned long asl = (unsigned long)data; - return set_acpi(asl, !blocked); -} + const char *method = cm_setv[cm]; + acpi_status status; -static const struct rfkill_ops eeepc_rfkill_ops = { - .set_block = eeepc_rfkill_set, -}; + if (method == NULL) + return -ENODEV; + if ((eeepc->cm_supported & (0x1 << cm)) == 0) + return -ENODEV; -static void __devinit eeepc_enable_camera(void) -{ - /* - * If the following call to set_acpi() fails, it's because there's no - * camera so we can ignore the error. - */ - if (get_acpi(CM_ASL_CAMERA) == 0) - set_acpi(CM_ASL_CAMERA, 1); + status = acpi_get_handle(eeepc->handle, (char *)method, + handle); + if (status != AE_OK) { + pr_warning("Error finding %s\n", method); + return -ENODEV; + } + return 0; } + /* * Sys helpers */ @@ -372,60 +277,63 @@ static int parse_arg(const char *buf, unsigned long count, int *val) return count; } -static ssize_t store_sys_acpi(int cm, const char *buf, size_t count) +static ssize_t store_sys_acpi(struct device *dev, int cm, + const char *buf, size_t count) { + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); int rv, value; rv = parse_arg(buf, count, &value); if (rv > 0) - value = set_acpi(cm, value); + value = set_acpi(eeepc, cm, value); if (value < 0) - return value; + return -EIO; return rv; } -static ssize_t show_sys_acpi(int cm, char *buf) +static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf) { - int value = get_acpi(cm); + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); + int value = get_acpi(eeepc, cm); if (value < 0) - return value; + return -EIO; return sprintf(buf, "%d\n", value); } -#define EEEPC_CREATE_DEVICE_ATTR(_name, _cm) \ +#define EEEPC_CREATE_DEVICE_ATTR(_name, _mode, _cm) \ static ssize_t show_##_name(struct device *dev, \ struct device_attribute *attr, \ char *buf) \ { \ - return show_sys_acpi(_cm, buf); \ + return show_sys_acpi(dev, _cm, buf); \ } \ static ssize_t store_##_name(struct device *dev, \ struct device_attribute *attr, \ const char *buf, size_t count) \ { \ - return store_sys_acpi(_cm, buf, count); \ + return store_sys_acpi(dev, _cm, buf, count); \ } \ static struct device_attribute dev_attr_##_name = { \ .attr = { \ .name = __stringify(_name), \ - .mode = 0644 }, \ + .mode = _mode }, \ .show = show_##_name, \ .store = store_##_name, \ } -EEEPC_CREATE_DEVICE_ATTR(camera, CM_ASL_CAMERA); -EEEPC_CREATE_DEVICE_ATTR(cardr, CM_ASL_CARDREADER); -EEEPC_CREATE_DEVICE_ATTR(disp, CM_ASL_DISPLAYSWITCH); +EEEPC_CREATE_DEVICE_ATTR(camera, 0644, CM_ASL_CAMERA); +EEEPC_CREATE_DEVICE_ATTR(cardr, 0644, CM_ASL_CARDREADER); +EEEPC_CREATE_DEVICE_ATTR(disp, 0200, CM_ASL_DISPLAYSWITCH); struct eeepc_cpufv { int num; int cur; }; -static int get_cpufv(struct eeepc_cpufv *c) +static int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c) { - c->cur = get_acpi(CM_ASL_CPUFV); + c->cur = get_acpi(eeepc, CM_ASL_CPUFV); c->num = (c->cur >> 8) & 0xff; c->cur &= 0xff; if (c->cur < 0 || c->num <= 0 || c->num > 12) @@ -437,11 +345,12 @@ static ssize_t show_available_cpufv(struct device *dev, struct device_attribute *attr, char *buf) { + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); struct eeepc_cpufv c; int i; ssize_t len = 0; - if (get_cpufv(&c)) + if (get_cpufv(eeepc, &c)) return -ENODEV; for (i = 0; i < c.num; i++) len += sprintf(buf + len, "%d ", i); @@ -453,9 +362,10 @@ static ssize_t show_cpufv(struct device *dev, struct device_attribute *attr, char *buf) { + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); struct eeepc_cpufv c; - if (get_cpufv(&c)) + if (get_cpufv(eeepc, &c)) return -ENODEV; return sprintf(buf, "%#x\n", (c.num << 8) | c.cur); } @@ -464,17 +374,18 @@ static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct eeepc_laptop *eeepc = dev_get_drvdata(dev); struct eeepc_cpufv c; int rv, value; - if (get_cpufv(&c)) + if (get_cpufv(eeepc, &c)) return -ENODEV; rv = parse_arg(buf, count, &value); if (rv < 0) return rv; if (!rv || value < 0 || value >= c.num) return -EINVAL; - set_acpi(CM_ASL_CPUFV, value); + set_acpi(eeepc, CM_ASL_CPUFV, value); return rv; } @@ -506,156 +417,125 @@ static struct attribute_group platform_attribute_group = { .attrs = platform_attributes }; -/* - * Hotkey functions - */ -static struct key_entry *eepc_get_entry_by_scancode(int code) +static int eeepc_platform_init(struct eeepc_laptop *eeepc) { - struct key_entry *key; - - for (key = eeepc_keymap; key->type != KE_END; key++) - if (code == key->code) - return key; + int result; - return NULL; -} + eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1); + if (!eeepc->platform_device) + return -ENOMEM; + platform_set_drvdata(eeepc->platform_device, eeepc); -static struct key_entry *eepc_get_entry_by_keycode(int code) -{ - struct key_entry *key; + result = platform_device_add(eeepc->platform_device); + if (result) + goto fail_platform_device; - for (key = eeepc_keymap; key->type != KE_END; key++) - if (code == key->keycode && key->type == KE_KEY) - return key; + result = sysfs_create_group(&eeepc->platform_device->dev.kobj, + &platform_attribute_group); + if (result) + goto fail_sysfs; + return 0; - return NULL; +fail_sysfs: + platform_device_del(eeepc->platform_device); +fail_platform_device: + platform_device_put(eeepc->platform_device); + return result; } -static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode) +static void eeepc_platform_exit(struct eeepc_laptop *eeepc) { - struct key_entry *key = eepc_get_entry_by_scancode(scancode); + sysfs_remove_group(&eeepc->platform_device->dev.kobj, + &platform_attribute_group); + platform_device_unregister(eeepc->platform_device); +} - if (key && key->type == KE_KEY) { - *keycode = key->keycode; - return 0; - } +/* + * LEDs + */ +/* + * These functions actually update the LED's, and are called from a + * workqueue. By doing this as separate work rather than when the LED + * subsystem asks, we avoid messing with the Asus ACPI stuff during a + * potentially bad time, such as a timer interrupt. + */ +static void tpd_led_update(struct work_struct *work) + { + struct eeepc_laptop *eeepc; - return -EINVAL; + eeepc = container_of(work, struct eeepc_laptop, tpd_led_work); + + set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk); } -static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode) +static void tpd_led_set(struct led_classdev *led_cdev, + enum led_brightness value) { - struct key_entry *key; - int old_keycode; + struct eeepc_laptop *eeepc; - if (keycode < 0 || keycode > KEY_MAX) - return -EINVAL; + eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led); - key = eepc_get_entry_by_scancode(scancode); - if (key && key->type == KE_KEY) { - old_keycode = key->keycode; - key->keycode = keycode; - set_bit(keycode, dev->keybit); - if (!eepc_get_entry_by_keycode(old_keycode)) - clear_bit(old_keycode, dev->keybit); - return 0; - } - - return -EINVAL; + eeepc->tpd_led_wk = (value > 0) ? 1 : 0; + queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work); } -static void cmsg_quirk(int cm, const char *name) +static int eeepc_led_init(struct eeepc_laptop *eeepc) { - int dummy; + int rv; - /* Some BIOSes do not report cm although it is avaliable. - Check if cm_getv[cm] works and, if yes, assume cm should be set. */ - if (!(ehotk->cm_supported & (1 << cm)) - && !read_acpi_int(ehotk->handle, cm_getv[cm], &dummy)) { - pr_info("%s (%x) not reported by BIOS," - " enabling anyway\n", name, 1 << cm); - ehotk->cm_supported |= 1 << cm; - } -} + if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV) + return 0; -static void cmsg_quirks(void) -{ - cmsg_quirk(CM_ASL_LID, "LID"); - cmsg_quirk(CM_ASL_TYPE, "TYPE"); - cmsg_quirk(CM_ASL_PANELPOWER, "PANELPOWER"); - cmsg_quirk(CM_ASL_TPD, "TPD"); -} + eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue"); + if (!eeepc->led_workqueue) + return -ENOMEM; + INIT_WORK(&eeepc->tpd_led_work, tpd_led_update); -static int eeepc_hotk_check(void) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - int result; + eeepc->tpd_led.name = "eeepc::touchpad"; + eeepc->tpd_led.brightness_set = tpd_led_set; + eeepc->tpd_led.max_brightness = 1; - result = acpi_bus_get_status(ehotk->device); - if (result) - return result; - if (ehotk->device->status.present) { - if (write_acpi_int(ehotk->handle, "INIT", ehotk->init_flag, - &buffer)) { - pr_err("Hotkey initialization failed\n"); - return -ENODEV; - } else { - pr_notice("Hotkey init flags 0x%x\n", ehotk->init_flag); - } - /* get control methods supported */ - if (read_acpi_int(ehotk->handle, "CMSG" - , &ehotk->cm_supported)) { - pr_err("Get control methods supported failed\n"); - return -ENODEV; - } else { - cmsg_quirks(); - pr_info("Get control methods supported: 0x%x\n", - ehotk->cm_supported); - } - } else { - pr_err("Hotkey device not present, aborting\n"); - return -EINVAL; + rv = led_classdev_register(&eeepc->platform_device->dev, + &eeepc->tpd_led); + if (rv) { + destroy_workqueue(eeepc->led_workqueue); + return rv; } + return 0; } -static int notify_brn(void) +static void eeepc_led_exit(struct eeepc_laptop *eeepc) { - /* returns the *previous* brightness, or -1 */ - struct backlight_device *bd = eeepc_backlight_device; - if (bd) { - int old = bd->props.brightness; - backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); - return old; - } - return -1; + if (eeepc->tpd_led.dev) + led_classdev_unregister(&eeepc->tpd_led); + if (eeepc->led_workqueue) + destroy_workqueue(eeepc->led_workqueue); } -static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot, - u8 *value) -{ - int val = get_acpi(CM_ASL_WLAN); - if (val == 1 || val == 0) - *value = val; - else - return -EINVAL; - - return 0; +/* + * PCI hotplug (for wlan rfkill) + */ +static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc) +{ + if (get_acpi(eeepc, CM_ASL_WLAN) == 1) + return false; + return true; } -static void eeepc_rfkill_hotplug(void) +static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc) { struct pci_dev *dev; struct pci_bus *bus; - bool blocked = eeepc_wlan_rfkill_blocked(); + bool blocked = eeepc_wlan_rfkill_blocked(eeepc); - if (ehotk->wlan_rfkill) - rfkill_set_sw_state(ehotk->wlan_rfkill, blocked); + if (eeepc->wlan_rfkill) + rfkill_set_sw_state(eeepc->wlan_rfkill, blocked); - mutex_lock(&ehotk->hotplug_lock); + mutex_lock(&eeepc->hotplug_lock); - if (ehotk->hotplug_slot) { + if (eeepc->hotplug_slot) { bus = pci_find_bus(0, 1); if (!bus) { pr_warning("Unable to find PCI bus 1?\n"); @@ -685,69 +565,23 @@ static void eeepc_rfkill_hotplug(void) } out_unlock: - mutex_unlock(&ehotk->hotplug_lock); + mutex_unlock(&eeepc->hotplug_lock); } static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data) { + struct eeepc_laptop *eeepc = data; + if (event != ACPI_NOTIFY_BUS_CHECK) return; - eeepc_rfkill_hotplug(); + eeepc_rfkill_hotplug(eeepc); } -static void eeepc_hotk_notify(struct acpi_device *device, u32 event) +static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc, + char *node) { - static struct key_entry *key; - u16 count; - int brn = -ENODEV; - - if (!ehotk) - return; - if (event > ACPI_MAX_SYS_NOTIFY) - return; - if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) - brn = notify_brn(); - count = ehotk->event_count[event % 128]++; - acpi_bus_generate_proc_event(ehotk->device, event, count); - acpi_bus_generate_netlink_event(ehotk->device->pnp.device_class, - dev_name(&ehotk->device->dev), event, - count); - if (ehotk->inputdev) { - if (brn != -ENODEV) { - /* brightness-change events need special - * handling for conversion to key events - */ - if (brn < 0) - brn = event; - else - brn += NOTIFY_BRN_MIN; - if (event < brn) - event = NOTIFY_BRN_MIN; /* brightness down */ - else if (event > brn) - event = NOTIFY_BRN_MIN + 2; /* ... up */ - else - event = NOTIFY_BRN_MIN + 1; /* ... unchanged */ - } - key = eepc_get_entry_by_scancode(event); - if (key) { - switch (key->type) { - case KE_KEY: - input_report_key(ehotk->inputdev, key->keycode, - 1); - input_sync(ehotk->inputdev); - input_report_key(ehotk->inputdev, key->keycode, - 0); - input_sync(ehotk->inputdev); - break; - } - } - } -} - -static int eeepc_register_rfkill_notifier(char *node) -{ - acpi_status status = AE_OK; + acpi_status status; acpi_handle handle; status = acpi_get_handle(NULL, node, &handle); @@ -756,7 +590,7 @@ static int eeepc_register_rfkill_notifier(char *node) status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, eeepc_rfkill_notify, - NULL); + eeepc); if (ACPI_FAILURE(status)) pr_warning("Failed to register notify on %s\n", node); } else @@ -765,7 +599,8 @@ static int eeepc_register_rfkill_notifier(char *node) return 0; } -static void eeepc_unregister_rfkill_notifier(char *node) +static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc, + char *node) { acpi_status status = AE_OK; acpi_handle handle; @@ -782,13 +617,33 @@ static void eeepc_unregister_rfkill_notifier(char *node) } } +static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot, + u8 *value) +{ + struct eeepc_laptop *eeepc = hotplug_slot->private; + int val = get_acpi(eeepc, CM_ASL_WLAN); + + if (val == 1 || val == 0) + *value = val; + else + return -EINVAL; + + return 0; +} + static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) { kfree(hotplug_slot->info); kfree(hotplug_slot); } -static int eeepc_setup_pci_hotplug(void) +static struct hotplug_slot_ops eeepc_hotplug_slot_ops = { + .owner = THIS_MODULE, + .get_adapter_status = eeepc_get_adapter_status, + .get_power_status = eeepc_get_adapter_status, +}; + +static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc) { int ret = -ENOMEM; struct pci_bus *bus = pci_find_bus(0, 1); @@ -798,22 +653,22 @@ static int eeepc_setup_pci_hotplug(void) return -ENODEV; } - ehotk->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); - if (!ehotk->hotplug_slot) + eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL); + if (!eeepc->hotplug_slot) goto error_slot; - ehotk->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), + eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); - if (!ehotk->hotplug_slot->info) + if (!eeepc->hotplug_slot->info) goto error_info; - ehotk->hotplug_slot->private = ehotk; - ehotk->hotplug_slot->release = &eeepc_cleanup_pci_hotplug; - ehotk->hotplug_slot->ops = &eeepc_hotplug_slot_ops; - eeepc_get_adapter_status(ehotk->hotplug_slot, - &ehotk->hotplug_slot->info->adapter_status); + eeepc->hotplug_slot->private = eeepc; + eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug; + eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops; + eeepc_get_adapter_status(eeepc->hotplug_slot, + &eeepc->hotplug_slot->info->adapter_status); - ret = pci_hp_register(ehotk->hotplug_slot, bus, 0, "eeepc-wifi"); + ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi"); if (ret) { pr_err("Unable to register hotplug slot - %d\n", ret); goto error_register; @@ -822,17 +677,156 @@ static int eeepc_setup_pci_hotplug(void) return 0; error_register: - kfree(ehotk->hotplug_slot->info); + kfree(eeepc->hotplug_slot->info); error_info: - kfree(ehotk->hotplug_slot); - ehotk->hotplug_slot = NULL; + kfree(eeepc->hotplug_slot); + eeepc->hotplug_slot = NULL; error_slot: return ret; } +/* + * Rfkill devices + */ +static int eeepc_rfkill_set(void *data, bool blocked) +{ + acpi_handle handle = data; + + return write_acpi_int(handle, NULL, !blocked); +} + +static const struct rfkill_ops eeepc_rfkill_ops = { + .set_block = eeepc_rfkill_set, +}; + +static int eeepc_new_rfkill(struct eeepc_laptop *eeepc, + struct rfkill **rfkill, + const char *name, + enum rfkill_type type, int cm) +{ + acpi_handle handle; + int result; + + result = acpi_setter_handle(eeepc, cm, &handle); + if (result < 0) + return result; + + *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type, + &eeepc_rfkill_ops, handle); + + if (!*rfkill) + return -EINVAL; + + rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1); + result = rfkill_register(*rfkill); + if (result) { + rfkill_destroy(*rfkill); + *rfkill = NULL; + return result; + } + return 0; +} + +static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc) +{ + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); + eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); + if (eeepc->wlan_rfkill) { + rfkill_unregister(eeepc->wlan_rfkill); + rfkill_destroy(eeepc->wlan_rfkill); + eeepc->wlan_rfkill = NULL; + } + /* + * Refresh pci hotplug in case the rfkill state was changed after + * eeepc_unregister_rfkill_notifier() + */ + eeepc_rfkill_hotplug(eeepc); + if (eeepc->hotplug_slot) + pci_hp_deregister(eeepc->hotplug_slot); + + if (eeepc->bluetooth_rfkill) { + rfkill_unregister(eeepc->bluetooth_rfkill); + rfkill_destroy(eeepc->bluetooth_rfkill); + eeepc->bluetooth_rfkill = NULL; + } + if (eeepc->wwan3g_rfkill) { + rfkill_unregister(eeepc->wwan3g_rfkill); + rfkill_destroy(eeepc->wwan3g_rfkill); + eeepc->wwan3g_rfkill = NULL; + } + if (eeepc->wimax_rfkill) { + rfkill_unregister(eeepc->wimax_rfkill); + rfkill_destroy(eeepc->wimax_rfkill); + eeepc->wimax_rfkill = NULL; + } +} + +static int eeepc_rfkill_init(struct eeepc_laptop *eeepc) +{ + int result = 0; + + mutex_init(&eeepc->hotplug_lock); + + result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill, + "eeepc-wlan", RFKILL_TYPE_WLAN, + CM_ASL_WLAN); + + if (result && result != -ENODEV) + goto exit; + + result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill, + "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH, + CM_ASL_BLUETOOTH); + + if (result && result != -ENODEV) + goto exit; + + result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill, + "eeepc-wwan3g", RFKILL_TYPE_WWAN, + CM_ASL_3G); + + if (result && result != -ENODEV) + goto exit; + + result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill, + "eeepc-wimax", RFKILL_TYPE_WIMAX, + CM_ASL_WIMAX); + + if (result && result != -ENODEV) + goto exit; + + result = eeepc_setup_pci_hotplug(eeepc); + /* + * If we get -EBUSY then something else is handling the PCI hotplug - + * don't fail in this case + */ + if (result == -EBUSY) + result = 0; + + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5"); + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6"); + eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7"); + /* + * Refresh pci hotplug in case the rfkill state was changed during + * setup. + */ + eeepc_rfkill_hotplug(eeepc); + +exit: + if (result && result != -ENODEV) + eeepc_rfkill_exit(eeepc); + return result; +} + +/* + * Platform driver - hibernate/resume callbacks + */ static int eeepc_hotk_thaw(struct device *device) { - if (ehotk->wlan_rfkill) { + struct eeepc_laptop *eeepc = dev_get_drvdata(device); + + if (eeepc->wlan_rfkill) { bool wlan; /* @@ -840,8 +834,8 @@ static int eeepc_hotk_thaw(struct device *device) * during suspend. Normally it restores it on resume, but * we should kick it ourselves in case hibernation is aborted. */ - wlan = get_acpi(CM_ASL_WLAN); - set_acpi(CM_ASL_WLAN, wlan); + wlan = get_acpi(eeepc, CM_ASL_WLAN); + set_acpi(eeepc, CM_ASL_WLAN, wlan); } return 0; @@ -849,70 +843,96 @@ static int eeepc_hotk_thaw(struct device *device) static int eeepc_hotk_restore(struct device *device) { + struct eeepc_laptop *eeepc = dev_get_drvdata(device); + /* Refresh both wlan rfkill state and pci hotplug */ - if (ehotk->wlan_rfkill) - eeepc_rfkill_hotplug(); - - if (ehotk->bluetooth_rfkill) - rfkill_set_sw_state(ehotk->bluetooth_rfkill, - get_acpi(CM_ASL_BLUETOOTH) != 1); - if (ehotk->wwan3g_rfkill) - rfkill_set_sw_state(ehotk->wwan3g_rfkill, - get_acpi(CM_ASL_3G) != 1); - if (ehotk->wimax_rfkill) - rfkill_set_sw_state(ehotk->wimax_rfkill, - get_acpi(CM_ASL_WIMAX) != 1); + if (eeepc->wlan_rfkill) + eeepc_rfkill_hotplug(eeepc); + + if (eeepc->bluetooth_rfkill) + rfkill_set_sw_state(eeepc->bluetooth_rfkill, + get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1); + if (eeepc->wwan3g_rfkill) + rfkill_set_sw_state(eeepc->wwan3g_rfkill, + get_acpi(eeepc, CM_ASL_3G) != 1); + if (eeepc->wimax_rfkill) + rfkill_set_sw_state(eeepc->wimax_rfkill, + get_acpi(eeepc, CM_ASL_WIMAX) != 1); return 0; } +static const struct dev_pm_ops eeepc_pm_ops = { + .thaw = eeepc_hotk_thaw, + .restore = eeepc_hotk_restore, +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = EEEPC_LAPTOP_FILE, + .owner = THIS_MODULE, + .pm = &eeepc_pm_ops, + } +}; + /* - * Hwmon + * Hwmon device */ + +#define EEEPC_EC_SC00 0x61 +#define EEEPC_EC_FAN_PWM (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */ +#define EEEPC_EC_FAN_HRPM (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */ +#define EEEPC_EC_FAN_LRPM (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */ + +#define EEEPC_EC_SFB0 0xD0 +#define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */ + static int eeepc_get_fan_pwm(void) { - int value = 0; + u8 value = 0; - read_acpi_int(NULL, EEEPC_EC_FAN_PWM, &value); - value = value * 255 / 100; - return (value); + ec_read(EEEPC_EC_FAN_PWM, &value); + return value * 255 / 100; } static void eeepc_set_fan_pwm(int value) { value = SENSORS_LIMIT(value, 0, 255); value = value * 100 / 255; - ec_write(EEEPC_EC_SC02, value); + ec_write(EEEPC_EC_FAN_PWM, value); } static int eeepc_get_fan_rpm(void) { - int high = 0; - int low = 0; + u8 high = 0; + u8 low = 0; - read_acpi_int(NULL, EEEPC_EC_FAN_HRPM, &high); - read_acpi_int(NULL, EEEPC_EC_FAN_LRPM, &low); - return (high << 8 | low); + ec_read(EEEPC_EC_FAN_HRPM, &high); + ec_read(EEEPC_EC_FAN_LRPM, &low); + return high << 8 | low; } static int eeepc_get_fan_ctrl(void) { - int value = 0; + u8 value = 0; - read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value); - return ((value & 0x02 ? 1 : 0)); + ec_read(EEEPC_EC_FAN_CTRL, &value); + if (value & 0x02) + return 1; /* manual */ + else + return 2; /* automatic */ } static void eeepc_set_fan_ctrl(int manual) { - int value = 0; + u8 value = 0; - read_acpi_int(NULL, EEEPC_EC_FAN_CTRL, &value); - if (manual) + ec_read(EEEPC_EC_FAN_CTRL, &value); + if (manual == 1) value |= 0x02; else value &= ~0x02; - ec_write(EEEPC_EC_SFB3, value); + ec_write(EEEPC_EC_FAN_CTRL, value); } static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count) @@ -970,348 +990,485 @@ static struct attribute_group hwmon_attribute_group = { .attrs = hwmon_attributes }; -/* - * exit/init - */ -static void eeepc_backlight_exit(void) +static void eeepc_hwmon_exit(struct eeepc_laptop *eeepc) { - if (eeepc_backlight_device) - backlight_device_unregister(eeepc_backlight_device); - eeepc_backlight_device = NULL; + struct device *hwmon; + + hwmon = eeepc->hwmon_device; + if (!hwmon) + return; + sysfs_remove_group(&hwmon->kobj, + &hwmon_attribute_group); + hwmon_device_unregister(hwmon); + eeepc->hwmon_device = NULL; } -static void eeepc_rfkill_exit(void) +static int eeepc_hwmon_init(struct eeepc_laptop *eeepc) { - eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P5"); - eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P6"); - eeepc_unregister_rfkill_notifier("\\_SB.PCI0.P0P7"); - if (ehotk->wlan_rfkill) { - rfkill_unregister(ehotk->wlan_rfkill); - rfkill_destroy(ehotk->wlan_rfkill); - ehotk->wlan_rfkill = NULL; - } - /* - * Refresh pci hotplug in case the rfkill state was changed after - * eeepc_unregister_rfkill_notifier() - */ - eeepc_rfkill_hotplug(); - if (ehotk->hotplug_slot) - pci_hp_deregister(ehotk->hotplug_slot); - - if (ehotk->bluetooth_rfkill) { - rfkill_unregister(ehotk->bluetooth_rfkill); - rfkill_destroy(ehotk->bluetooth_rfkill); - ehotk->bluetooth_rfkill = NULL; - } - if (ehotk->wwan3g_rfkill) { - rfkill_unregister(ehotk->wwan3g_rfkill); - rfkill_destroy(ehotk->wwan3g_rfkill); - ehotk->wwan3g_rfkill = NULL; - } - if (ehotk->wimax_rfkill) { - rfkill_unregister(ehotk->wimax_rfkill); - rfkill_destroy(ehotk->wimax_rfkill); - ehotk->wimax_rfkill = NULL; + struct device *hwmon; + int result; + + hwmon = hwmon_device_register(&eeepc->platform_device->dev); + if (IS_ERR(hwmon)) { + pr_err("Could not register eeepc hwmon device\n"); + eeepc->hwmon_device = NULL; + return PTR_ERR(hwmon); } + eeepc->hwmon_device = hwmon; + result = sysfs_create_group(&hwmon->kobj, + &hwmon_attribute_group); + if (result) + eeepc_hwmon_exit(eeepc); + return result; } -static void eeepc_input_exit(void) +/* + * Backlight device + */ +static int read_brightness(struct backlight_device *bd) { - if (ehotk->inputdev) - input_unregister_device(ehotk->inputdev); + struct eeepc_laptop *eeepc = bl_get_data(bd); + + return get_acpi(eeepc, CM_ASL_PANELBRIGHT); } -static void eeepc_hwmon_exit(void) +static int set_brightness(struct backlight_device *bd, int value) { - struct device *hwmon; + struct eeepc_laptop *eeepc = bl_get_data(bd); - hwmon = eeepc_hwmon_device; - if (!hwmon) - return ; - sysfs_remove_group(&hwmon->kobj, - &hwmon_attribute_group); - hwmon_device_unregister(hwmon); - eeepc_hwmon_device = NULL; + return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value); } -static int eeepc_new_rfkill(struct rfkill **rfkill, - const char *name, struct device *dev, - enum rfkill_type type, int cm) +static int update_bl_status(struct backlight_device *bd) { - int result; + return set_brightness(bd, bd->props.brightness); +} - result = get_acpi(cm); - if (result < 0) - return result; +static struct backlight_ops eeepcbl_ops = { + .get_brightness = read_brightness, + .update_status = update_bl_status, +}; - *rfkill = rfkill_alloc(name, dev, type, - &eeepc_rfkill_ops, (void *)(unsigned long)cm); +static int eeepc_backlight_notify(struct eeepc_laptop *eeepc) +{ + struct backlight_device *bd = eeepc->backlight_device; + int old = bd->props.brightness; - if (!*rfkill) - return -EINVAL; + backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY); - rfkill_init_sw_state(*rfkill, get_acpi(cm) != 1); - result = rfkill_register(*rfkill); - if (result) { - rfkill_destroy(*rfkill); - *rfkill = NULL; - return result; - } - return 0; + return old; } - -static int eeepc_rfkill_init(struct device *dev) +static int eeepc_backlight_init(struct eeepc_laptop *eeepc) { - int result = 0; - - mutex_init(&ehotk->hotplug_lock); + struct backlight_device *bd; - result = eeepc_new_rfkill(&ehotk->wlan_rfkill, - "eeepc-wlan", dev, - RFKILL_TYPE_WLAN, CM_ASL_WLAN); + bd = backlight_device_register(EEEPC_LAPTOP_FILE, + &eeepc->platform_device->dev, + eeepc, &eeepcbl_ops); + if (IS_ERR(bd)) { + pr_err("Could not register eeepc backlight device\n"); + eeepc->backlight_device = NULL; + return PTR_ERR(bd); + } + eeepc->backlight_device = bd; + bd->props.max_brightness = 15; + bd->props.brightness = read_brightness(bd); + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + return 0; +} - if (result && result != -ENODEV) - goto exit; +static void eeepc_backlight_exit(struct eeepc_laptop *eeepc) +{ + if (eeepc->backlight_device) + backlight_device_unregister(eeepc->backlight_device); + eeepc->backlight_device = NULL; +} - result = eeepc_new_rfkill(&ehotk->bluetooth_rfkill, - "eeepc-bluetooth", dev, - RFKILL_TYPE_BLUETOOTH, CM_ASL_BLUETOOTH); - if (result && result != -ENODEV) - goto exit; +/* + * Input device (i.e. hotkeys) + */ +static struct key_entry *eeepc_get_entry_by_scancode( + struct eeepc_laptop *eeepc, + int code) +{ + struct key_entry *key; - result = eeepc_new_rfkill(&ehotk->wwan3g_rfkill, - "eeepc-wwan3g", dev, - RFKILL_TYPE_WWAN, CM_ASL_3G); + for (key = eeepc->keymap; key->type != KE_END; key++) + if (code == key->code) + return key; - if (result && result != -ENODEV) - goto exit; + return NULL; +} - result = eeepc_new_rfkill(&ehotk->wimax_rfkill, - "eeepc-wimax", dev, - RFKILL_TYPE_WIMAX, CM_ASL_WIMAX); +static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event) +{ + static struct key_entry *key; - if (result && result != -ENODEV) - goto exit; + key = eeepc_get_entry_by_scancode(eeepc, event); + if (key) { + switch (key->type) { + case KE_KEY: + input_report_key(eeepc->inputdev, key->keycode, + 1); + input_sync(eeepc->inputdev); + input_report_key(eeepc->inputdev, key->keycode, + 0); + input_sync(eeepc->inputdev); + break; + } + } +} - result = eeepc_setup_pci_hotplug(); - /* - * If we get -EBUSY then something else is handling the PCI hotplug - - * don't fail in this case - */ - if (result == -EBUSY) - result = 0; +static struct key_entry *eeepc_get_entry_by_keycode( + struct eeepc_laptop *eeepc, int code) +{ + struct key_entry *key; - eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P5"); - eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P6"); - eeepc_register_rfkill_notifier("\\_SB.PCI0.P0P7"); - /* - * Refresh pci hotplug in case the rfkill state was changed during - * setup. - */ - eeepc_rfkill_hotplug(); + for (key = eeepc->keymap; key->type != KE_END; key++) + if (code == key->keycode && key->type == KE_KEY) + return key; -exit: - if (result && result != -ENODEV) - eeepc_rfkill_exit(); - return result; + return NULL; } -static int eeepc_backlight_init(struct device *dev) +static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode) { - struct backlight_device *bd; + struct eeepc_laptop *eeepc = input_get_drvdata(dev); + struct key_entry *key = eeepc_get_entry_by_scancode(eeepc, scancode); - bd = backlight_device_register(EEEPC_HOTK_FILE, dev, - NULL, &eeepcbl_ops); - if (IS_ERR(bd)) { - pr_err("Could not register eeepc backlight device\n"); - eeepc_backlight_device = NULL; - return PTR_ERR(bd); + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; } - eeepc_backlight_device = bd; - bd->props.max_brightness = 15; - bd->props.brightness = read_brightness(NULL); - bd->props.power = FB_BLANK_UNBLANK; - backlight_update_status(bd); - return 0; + + return -EINVAL; } -static int eeepc_hwmon_init(struct device *dev) +static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode) { - struct device *hwmon; - int result; + struct eeepc_laptop *eeepc = input_get_drvdata(dev); + struct key_entry *key; + int old_keycode; - hwmon = hwmon_device_register(dev); - if (IS_ERR(hwmon)) { - pr_err("Could not register eeepc hwmon device\n"); - eeepc_hwmon_device = NULL; - return PTR_ERR(hwmon); + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + key = eeepc_get_entry_by_scancode(eeepc, scancode); + if (key && key->type == KE_KEY) { + old_keycode = key->keycode; + key->keycode = keycode; + set_bit(keycode, dev->keybit); + if (!eeepc_get_entry_by_keycode(eeepc, old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; } - eeepc_hwmon_device = hwmon; - result = sysfs_create_group(&hwmon->kobj, - &hwmon_attribute_group); - if (result) - eeepc_hwmon_exit(); - return result; + + return -EINVAL; } -static int eeepc_input_init(struct device *dev) +static int eeepc_input_init(struct eeepc_laptop *eeepc) { const struct key_entry *key; int result; - ehotk->inputdev = input_allocate_device(); - if (!ehotk->inputdev) { + eeepc->inputdev = input_allocate_device(); + if (!eeepc->inputdev) { pr_info("Unable to allocate input device\n"); return -ENOMEM; } - ehotk->inputdev->name = "Asus EeePC extra buttons"; - ehotk->inputdev->dev.parent = dev; - ehotk->inputdev->phys = EEEPC_HOTK_FILE "/input0"; - ehotk->inputdev->id.bustype = BUS_HOST; - ehotk->inputdev->getkeycode = eeepc_getkeycode; - ehotk->inputdev->setkeycode = eeepc_setkeycode; - + eeepc->inputdev->name = "Asus EeePC extra buttons"; + eeepc->inputdev->dev.parent = &eeepc->platform_device->dev; + eeepc->inputdev->phys = EEEPC_LAPTOP_FILE "/input0"; + eeepc->inputdev->id.bustype = BUS_HOST; + eeepc->inputdev->getkeycode = eeepc_getkeycode; + eeepc->inputdev->setkeycode = eeepc_setkeycode; + input_set_drvdata(eeepc->inputdev, eeepc); + + eeepc->keymap = kmemdup(eeepc_keymap, sizeof(eeepc_keymap), + GFP_KERNEL); for (key = eeepc_keymap; key->type != KE_END; key++) { switch (key->type) { case KE_KEY: - set_bit(EV_KEY, ehotk->inputdev->evbit); - set_bit(key->keycode, ehotk->inputdev->keybit); + set_bit(EV_KEY, eeepc->inputdev->evbit); + set_bit(key->keycode, eeepc->inputdev->keybit); break; } } - result = input_register_device(ehotk->inputdev); + result = input_register_device(eeepc->inputdev); if (result) { pr_info("Unable to register input device\n"); - input_free_device(ehotk->inputdev); + input_free_device(eeepc->inputdev); return result; } return 0; } -static int __devinit eeepc_hotk_add(struct acpi_device *device) +static void eeepc_input_exit(struct eeepc_laptop *eeepc) { - struct device *dev; + if (eeepc->inputdev) { + input_unregister_device(eeepc->inputdev); + kfree(eeepc->keymap); + } +} + +/* + * ACPI driver + */ +static void eeepc_acpi_notify(struct acpi_device *device, u32 event) +{ + struct eeepc_laptop *eeepc = acpi_driver_data(device); + u16 count; + + if (event > ACPI_MAX_SYS_NOTIFY) + return; + count = eeepc->event_count[event % 128]++; + acpi_bus_generate_proc_event(device, event, count); + acpi_bus_generate_netlink_event(device->pnp.device_class, + dev_name(&device->dev), event, + count); + + /* Brightness events are special */ + if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) { + + /* Ignore them completely if the acpi video driver is used */ + if (eeepc->backlight_device != NULL) { + int old_brightness, new_brightness; + + /* Update the backlight device. */ + old_brightness = eeepc_backlight_notify(eeepc); + + /* Convert event to keypress (obsolescent hack) */ + new_brightness = event - NOTIFY_BRN_MIN; + + if (new_brightness < old_brightness) { + event = NOTIFY_BRN_MIN; /* brightness down */ + } else if (new_brightness > old_brightness) { + event = NOTIFY_BRN_MAX; /* brightness up */ + } else { + /* + * no change in brightness - already at min/max, + * event will be desired value (or else ignored) + */ + } + eeepc_input_notify(eeepc, event); + } + } else { + /* Everything else is a bona-fide keypress event */ + eeepc_input_notify(eeepc, event); + } +} + +static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name) +{ + int dummy; + + /* Some BIOSes do not report cm although it is avaliable. + Check if cm_getv[cm] works and, if yes, assume cm should be set. */ + if (!(eeepc->cm_supported & (1 << cm)) + && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) { + pr_info("%s (%x) not reported by BIOS," + " enabling anyway\n", name, 1 << cm); + eeepc->cm_supported |= 1 << cm; + } +} + +static void cmsg_quirks(struct eeepc_laptop *eeepc) +{ + cmsg_quirk(eeepc, CM_ASL_LID, "LID"); + cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE"); + cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER"); + cmsg_quirk(eeepc, CM_ASL_TPD, "TPD"); +} + +static int eeepc_acpi_init(struct eeepc_laptop *eeepc, + struct acpi_device *device) +{ + unsigned int init_flags; int result; - if (!device) - return -EINVAL; - pr_notice(EEEPC_HOTK_NAME "\n"); - ehotk = kzalloc(sizeof(struct eeepc_hotk), GFP_KERNEL); - if (!ehotk) - return -ENOMEM; - ehotk->init_flag = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH; - ehotk->handle = device->handle; - strcpy(acpi_device_name(device), EEEPC_HOTK_DEVICE_NAME); - strcpy(acpi_device_class(device), EEEPC_HOTK_CLASS); - device->driver_data = ehotk; - ehotk->device = device; - - result = eeepc_hotk_check(); + result = acpi_bus_get_status(device); if (result) - goto fail_platform_driver; - eeepc_enable_camera(); + return result; + if (!device->status.present) { + pr_err("Hotkey device not present, aborting\n"); + return -ENODEV; + } - /* Register platform stuff */ - result = platform_driver_register(&platform_driver); - if (result) - goto fail_platform_driver; - platform_device = platform_device_alloc(EEEPC_HOTK_FILE, -1); - if (!platform_device) { - result = -ENOMEM; - goto fail_platform_device1; + init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH; + pr_notice("Hotkey init flags 0x%x\n", init_flags); + + if (write_acpi_int(eeepc->handle, "INIT", init_flags)) { + pr_err("Hotkey initialization failed\n"); + return -ENODEV; } - result = platform_device_add(platform_device); - if (result) - goto fail_platform_device2; - result = sysfs_create_group(&platform_device->dev.kobj, - &platform_attribute_group); + + /* get control methods supported */ + if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) { + pr_err("Get control methods supported failed\n"); + return -ENODEV; + } + cmsg_quirks(eeepc); + pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported); + + return 0; +} + +static void __devinit eeepc_enable_camera(struct eeepc_laptop *eeepc) +{ + /* + * If the following call to set_acpi() fails, it's because there's no + * camera so we can ignore the error. + */ + if (get_acpi(eeepc, CM_ASL_CAMERA) == 0) + set_acpi(eeepc, CM_ASL_CAMERA, 1); +} + +static bool eeepc_device_present; + +static int __devinit eeepc_acpi_add(struct acpi_device *device) +{ + struct eeepc_laptop *eeepc; + int result; + + pr_notice(EEEPC_LAPTOP_NAME "\n"); + eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL); + if (!eeepc) + return -ENOMEM; + eeepc->handle = device->handle; + strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); + strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + device->driver_data = eeepc; + + result = eeepc_acpi_init(eeepc, device); if (result) - goto fail_sysfs; + goto fail_platform; + eeepc_enable_camera(eeepc); - dev = &platform_device->dev; + /* + * Register the platform device first. It is used as a parent for the + * sub-devices below. + * + * Note that if there are multiple instances of this ACPI device it + * will bail out, because the platform device is registered with a + * fixed name. Of course it doesn't make sense to have more than one, + * and machine-specific scripts find the fixed name convenient. But + * It's also good for us to exclude multiple instances because both + * our hwmon and our wlan rfkill subdevice use global ACPI objects + * (the EC and the wlan PCI slot respectively). + */ + result = eeepc_platform_init(eeepc); + if (result) + goto fail_platform; if (!acpi_video_backlight_support()) { - result = eeepc_backlight_init(dev); + result = eeepc_backlight_init(eeepc); if (result) goto fail_backlight; } else - pr_info("Backlight controlled by ACPI video " - "driver\n"); + pr_info("Backlight controlled by ACPI video driver\n"); - result = eeepc_input_init(dev); + result = eeepc_input_init(eeepc); if (result) goto fail_input; - result = eeepc_hwmon_init(dev); + result = eeepc_hwmon_init(eeepc); if (result) goto fail_hwmon; - result = eeepc_rfkill_init(dev); + result = eeepc_led_init(eeepc); + if (result) + goto fail_led; + + result = eeepc_rfkill_init(eeepc); if (result) goto fail_rfkill; + eeepc_device_present = true; return 0; fail_rfkill: - eeepc_hwmon_exit(); + eeepc_led_exit(eeepc); +fail_led: + eeepc_hwmon_exit(eeepc); fail_hwmon: - eeepc_input_exit(); + eeepc_input_exit(eeepc); fail_input: - eeepc_backlight_exit(); + eeepc_backlight_exit(eeepc); fail_backlight: - sysfs_remove_group(&platform_device->dev.kobj, - &platform_attribute_group); -fail_sysfs: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - kfree(ehotk); + eeepc_platform_exit(eeepc); +fail_platform: + kfree(eeepc); return result; } -static int eeepc_hotk_remove(struct acpi_device *device, int type) +static int eeepc_acpi_remove(struct acpi_device *device, int type) { - if (!device || !acpi_driver_data(device)) - return -EINVAL; + struct eeepc_laptop *eeepc = acpi_driver_data(device); - eeepc_backlight_exit(); - eeepc_rfkill_exit(); - eeepc_input_exit(); - eeepc_hwmon_exit(); - sysfs_remove_group(&platform_device->dev.kobj, - &platform_attribute_group); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); + eeepc_backlight_exit(eeepc); + eeepc_rfkill_exit(eeepc); + eeepc_input_exit(eeepc); + eeepc_hwmon_exit(eeepc); + eeepc_led_exit(eeepc); + eeepc_platform_exit(eeepc); - kfree(ehotk); + kfree(eeepc); return 0; } + +static const struct acpi_device_id eeepc_device_ids[] = { + {EEEPC_ACPI_HID, 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, eeepc_device_ids); + +static struct acpi_driver eeepc_acpi_driver = { + .name = EEEPC_LAPTOP_NAME, + .class = EEEPC_ACPI_CLASS, + .owner = THIS_MODULE, + .ids = eeepc_device_ids, + .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, + .ops = { + .add = eeepc_acpi_add, + .remove = eeepc_acpi_remove, + .notify = eeepc_acpi_notify, + }, +}; + + static int __init eeepc_laptop_init(void) { int result; - if (acpi_disabled) - return -ENODEV; - result = acpi_bus_register_driver(&eeepc_hotk_driver); + result = platform_driver_register(&platform_driver); if (result < 0) return result; - if (!ehotk) { - acpi_bus_unregister_driver(&eeepc_hotk_driver); - return -ENODEV; + + result = acpi_bus_register_driver(&eeepc_acpi_driver); + if (result < 0) + goto fail_acpi_driver; + if (!eeepc_device_present) { + result = -ENODEV; + goto fail_no_device; } return 0; + +fail_no_device: + acpi_bus_unregister_driver(&eeepc_acpi_driver); +fail_acpi_driver: + platform_driver_unregister(&platform_driver); + return result; } static void __exit eeepc_laptop_exit(void) { - acpi_bus_unregister_driver(&eeepc_hotk_driver); + acpi_bus_unregister_driver(&eeepc_acpi_driver); + platform_driver_unregister(&platform_driver); } module_init(eeepc_laptop_init); diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index f00a71c58e69..63c3e658a884 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -51,6 +51,12 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); #define HPWMI_WIRELESS_QUERY 0x5 #define HPWMI_HOTKEY_QUERY 0xc +enum hp_wmi_radio { + HPWMI_WIFI = 0, + HPWMI_BLUETOOTH = 1, + HPWMI_WWAN = 2, +}; + static int __init hp_wmi_bios_setup(struct platform_device *device); static int __exit hp_wmi_bios_remove(struct platform_device *device); static int hp_wmi_resume_handler(struct device *device); @@ -175,8 +181,8 @@ static int hp_wmi_tablet_state(void) static int hp_wmi_set_block(void *data, bool blocked) { - unsigned long b = (unsigned long) data; - int query = BIT(b + 8) | ((!blocked) << b); + enum hp_wmi_radio r = (enum hp_wmi_radio) data; + int query = BIT(r + 8) | ((!blocked) << r); return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, query); } @@ -185,31 +191,23 @@ static const struct rfkill_ops hp_wmi_rfkill_ops = { .set_block = hp_wmi_set_block, }; -static bool hp_wmi_wifi_state(void) -{ - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); - - if (wireless & 0x100) - return false; - else - return true; -} - -static bool hp_wmi_bluetooth_state(void) +static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) { int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + int mask = 0x200 << (r * 8); - if (wireless & 0x10000) + if (wireless & mask) return false; else return true; } -static bool hp_wmi_wwan_state(void) +static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) { int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + int mask = 0x800 << (r * 8); - if (wireless & 0x1000000) + if (wireless & mask) return false; else return true; @@ -334,49 +332,55 @@ static void hp_wmi_notify(u32 value, void *context) struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; static struct key_entry *key; union acpi_object *obj; + int eventcode; wmi_get_event_data(value, &response); obj = (union acpi_object *)response.pointer; - if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == 8) { - int eventcode = *((u8 *) obj->buffer.pointer); - if (eventcode == 0x4) - eventcode = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, - 0); - key = hp_wmi_get_entry_by_scancode(eventcode); - if (key) { - switch (key->type) { - case KE_KEY: - input_report_key(hp_wmi_input_dev, - key->keycode, 1); - input_sync(hp_wmi_input_dev); - input_report_key(hp_wmi_input_dev, - key->keycode, 0); - input_sync(hp_wmi_input_dev); - break; - } - } else if (eventcode == 0x1) { - input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_dock_state()); - input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_tablet_state()); + if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) { + printk(KERN_INFO "HP WMI: Unknown response received\n"); + return; + } + + eventcode = *((u8 *) obj->buffer.pointer); + if (eventcode == 0x4) + eventcode = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, + 0); + key = hp_wmi_get_entry_by_scancode(eventcode); + if (key) { + switch (key->type) { + case KE_KEY: + input_report_key(hp_wmi_input_dev, + key->keycode, 1); + input_sync(hp_wmi_input_dev); + input_report_key(hp_wmi_input_dev, + key->keycode, 0); input_sync(hp_wmi_input_dev); - } else if (eventcode == 0x5) { - if (wifi_rfkill) - rfkill_set_sw_state(wifi_rfkill, - hp_wmi_wifi_state()); - if (bluetooth_rfkill) - rfkill_set_sw_state(bluetooth_rfkill, - hp_wmi_bluetooth_state()); - if (wwan_rfkill) - rfkill_set_sw_state(wwan_rfkill, - hp_wmi_wwan_state()); - } else - printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n", - eventcode); + break; + } + } else if (eventcode == 0x1) { + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_dock_state()); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_tablet_state()); + input_sync(hp_wmi_input_dev); + } else if (eventcode == 0x5) { + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); } else - printk(KERN_INFO "HP WMI: Unknown response received\n"); + printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n", + eventcode); } static int __init hp_wmi_input_setup(void) @@ -455,7 +459,11 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev, RFKILL_TYPE_WLAN, &hp_wmi_rfkill_ops, - (void *) 0); + (void *) HPWMI_WIFI); + rfkill_init_sw_state(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI)); + rfkill_set_hw_state(wifi_rfkill, + hp_wmi_get_hw_state(HPWMI_WIFI)); err = rfkill_register(wifi_rfkill); if (err) goto register_wifi_error; @@ -465,7 +473,11 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev, RFKILL_TYPE_BLUETOOTH, &hp_wmi_rfkill_ops, - (void *) 1); + (void *) HPWMI_BLUETOOTH); + rfkill_init_sw_state(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH)); + rfkill_set_hw_state(bluetooth_rfkill, + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); err = rfkill_register(bluetooth_rfkill); if (err) goto register_bluetooth_error; @@ -475,7 +487,11 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev, RFKILL_TYPE_WWAN, &hp_wmi_rfkill_ops, - (void *) 2); + (void *) HPWMI_WWAN); + rfkill_init_sw_state(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN)); + rfkill_set_hw_state(wwan_rfkill, + hp_wmi_get_hw_state(HPWMI_WWAN)); err = rfkill_register(wwan_rfkill); if (err) goto register_wwan_err; @@ -533,6 +549,19 @@ static int hp_wmi_resume_handler(struct device *device) input_sync(hp_wmi_input_dev); } + if (wifi_rfkill) + rfkill_set_states(wifi_rfkill, + hp_wmi_get_sw_state(HPWMI_WIFI), + hp_wmi_get_hw_state(HPWMI_WIFI)); + if (bluetooth_rfkill) + rfkill_set_states(bluetooth_rfkill, + hp_wmi_get_sw_state(HPWMI_BLUETOOTH), + hp_wmi_get_hw_state(HPWMI_BLUETOOTH)); + if (wwan_rfkill) + rfkill_set_states(wwan_rfkill, + hp_wmi_get_sw_state(HPWMI_WWAN), + hp_wmi_get_hw_state(HPWMI_WWAN)); + return 0; } diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c new file mode 100644 index 000000000000..0c8fe145c4af --- /dev/null +++ b/drivers/platform/x86/msi-wmi.c @@ -0,0 +1,293 @@ +/* + * MSI WMI hotkeys + * + * Copyright (C) 2009 Novell <trenn@suse.de> + * + * Most stuff taken over from hp-wmi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/acpi.h> +#include <linux/backlight.h> + +MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>"); +MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("wmi:551A1F84-FBDD-4125-91DB-3EA8F44F1D45"); +MODULE_ALIAS("wmi:B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"); + +/* Temporary workaround until the WMI sysfs interface goes in + { "svn", DMI_SYS_VENDOR }, + { "pn", DMI_PRODUCT_NAME }, + { "pvr", DMI_PRODUCT_VERSION }, + { "rvn", DMI_BOARD_VENDOR }, + { "rn", DMI_BOARD_NAME }, +*/ + +MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-6638:*"); + +#define DRV_NAME "msi-wmi" +#define DRV_PFX DRV_NAME ": " + +#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45" +#define MSIWMI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2" + +#define dprintk(msg...) pr_debug(DRV_PFX msg) + +#define KEYCODE_BASE 0xD0 +#define MSI_WMI_BRIGHTNESSUP KEYCODE_BASE +#define MSI_WMI_BRIGHTNESSDOWN (KEYCODE_BASE + 1) +#define MSI_WMI_VOLUMEUP (KEYCODE_BASE + 2) +#define MSI_WMI_VOLUMEDOWN (KEYCODE_BASE + 3) +static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_WMI_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, + { KE_KEY, MSI_WMI_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} }, + { KE_KEY, MSI_WMI_VOLUMEUP, {KEY_VOLUMEUP} }, + { KE_KEY, MSI_WMI_VOLUMEDOWN, {KEY_VOLUMEDOWN} }, + { KE_END, 0} +}; +static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1]; + +struct backlight_device *backlight; + +static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; + +static struct input_dev *msi_wmi_input_dev; + +static int msi_wmi_query_block(int instance, int *ret) +{ + acpi_status status; + union acpi_object *obj; + + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); + + obj = output.pointer; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) { + if (obj) { + printk(KERN_ERR DRV_PFX "query block returned object " + "type: %d - buffer length:%d\n", obj->type, + obj->type == ACPI_TYPE_BUFFER ? + obj->buffer.length : 0); + } + kfree(obj); + return -EINVAL; + } + *ret = obj->integer.value; + kfree(obj); + return 0; +} + +static int msi_wmi_set_block(int instance, int value) +{ + acpi_status status; + + struct acpi_buffer input = { sizeof(int), &value }; + + dprintk("Going to set block of instance: %d - value: %d\n", + instance, value); + + status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input); + + return ACPI_SUCCESS(status) ? 0 : 1; +} + +static int bl_get(struct backlight_device *bd) +{ + int level, err, ret; + + /* Instance 1 is "get backlight", cmp with DSDT */ + err = msi_wmi_query_block(1, &ret); + if (err) { + printk(KERN_ERR DRV_PFX "Could not query backlight: %d\n", err); + return -EINVAL; + } + dprintk("Get: Query block returned: %d\n", ret); + for (level = 0; level < ARRAY_SIZE(backlight_map); level++) { + if (backlight_map[level] == ret) { + dprintk("Current backlight level: 0x%X - index: %d\n", + backlight_map[level], level); + break; + } + } + if (level == ARRAY_SIZE(backlight_map)) { + printk(KERN_ERR DRV_PFX "get: Invalid brightness value: 0x%X\n", + ret); + return -EINVAL; + } + return level; +} + +static int bl_set_status(struct backlight_device *bd) +{ + int bright = bd->props.brightness; + if (bright >= ARRAY_SIZE(backlight_map) || bright < 0) + return -EINVAL; + + /* Instance 0 is "set backlight" */ + return msi_wmi_set_block(0, backlight_map[bright]); +} + +static struct backlight_ops msi_backlight_ops = { + .get_brightness = bl_get, + .update_status = bl_set_status, +}; + +static void msi_wmi_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + static struct key_entry *key; + union acpi_object *obj; + ktime_t cur; + + wmi_get_event_data(value, &response); + + obj = (union acpi_object *)response.pointer; + + if (obj && obj->type == ACPI_TYPE_INTEGER) { + int eventcode = obj->integer.value; + dprintk("Eventcode: 0x%x\n", eventcode); + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, + eventcode); + if (key) { + ktime_t diff; + cur = ktime_get_real(); + diff = ktime_sub(cur, last_pressed[key->code - + KEYCODE_BASE]); + /* Ignore event if the same event happened in a 50 ms + timeframe -> Key press may result in 10-20 GPEs */ + if (ktime_to_us(diff) < 1000 * 50) { + dprintk("Suppressed key event 0x%X - " + "Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); + return; + } + last_pressed[key->code - KEYCODE_BASE] = cur; + + if (key->type == KE_KEY && + /* Brightness is served via acpi video driver */ + (!acpi_video_backlight_support() || + (key->code != MSI_WMI_BRIGHTNESSUP && + key->code != MSI_WMI_BRIGHTNESSDOWN))) { + dprintk("Send key: 0x%X - " + "Input layer keycode: %d\n", key->code, + key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, + key, 1, true); + } + } else + printk(KERN_INFO "Unknown key pressed - %x\n", + eventcode); + } else + printk(KERN_INFO DRV_PFX "Unknown event received\n"); + kfree(response.pointer); +} + +static int __init msi_wmi_input_setup(void) +{ + int err; + + msi_wmi_input_dev = input_allocate_device(); + if (!msi_wmi_input_dev) + return -ENOMEM; + + msi_wmi_input_dev->name = "MSI WMI hotkeys"; + msi_wmi_input_dev->phys = "wmi/input0"; + msi_wmi_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL); + if (err) + goto err_free_dev; + + err = input_register_device(msi_wmi_input_dev); + + if (err) + goto err_free_keymap; + + memset(last_pressed, 0, sizeof(last_pressed)); + + return 0; + +err_free_keymap: + sparse_keymap_free(msi_wmi_input_dev); +err_free_dev: + input_free_device(msi_wmi_input_dev); + return err; +} + +static int __init msi_wmi_init(void) +{ + int err; + + if (!wmi_has_guid(MSIWMI_EVENT_GUID)) { + printk(KERN_ERR + "This machine doesn't have MSI-hotkeys through WMI\n"); + return -ENODEV; + } + err = wmi_install_notify_handler(MSIWMI_EVENT_GUID, + msi_wmi_notify, NULL); + if (err) + return -EINVAL; + + err = msi_wmi_input_setup(); + if (err) + goto err_uninstall_notifier; + + if (!acpi_video_backlight_support()) { + backlight = backlight_device_register(DRV_NAME, + NULL, NULL, &msi_backlight_ops); + if (IS_ERR(backlight)) + goto err_free_input; + + backlight->props.max_brightness = ARRAY_SIZE(backlight_map) - 1; + err = bl_get(NULL); + if (err < 0) + goto err_free_backlight; + + backlight->props.brightness = err; + } + dprintk("Event handler installed\n"); + + return 0; + +err_free_backlight: + backlight_device_unregister(backlight); +err_free_input: + input_unregister_device(msi_wmi_input_dev); +err_uninstall_notifier: + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + return err; +} + +static void __exit msi_wmi_exit(void) +{ + if (wmi_has_guid(MSIWMI_EVENT_GUID)) { + wmi_remove_notify_handler(MSIWMI_EVENT_GUID); + sparse_keymap_free(msi_wmi_input_dev); + input_unregister_device(msi_wmi_input_dev); + backlight_device_unregister(backlight); + } +} + +module_init(msi_wmi_init); +module_exit(msi_wmi_exit); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index cf61d6a8ef6f..448c8aeb166b 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -21,8 +21,8 @@ * 02110-1301, USA. */ -#define TPACPI_VERSION "0.23" -#define TPACPI_SYSFS_VERSION 0x020500 +#define TPACPI_VERSION "0.24" +#define TPACPI_SYSFS_VERSION 0x020700 /* * Changelog: @@ -61,6 +61,7 @@ #include <linux/nvram.h> #include <linux/proc_fs.h> +#include <linux/seq_file.h> #include <linux/sysfs.h> #include <linux/backlight.h> #include <linux/fb.h> @@ -76,6 +77,10 @@ #include <linux/jiffies.h> #include <linux/workqueue.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/initval.h> + #include <acpi/acpi_drivers.h> #include <linux/pci_ids.h> @@ -231,6 +236,7 @@ enum tpacpi_hkey_event_t { #define TPACPI_DBG_HKEY 0x0008 #define TPACPI_DBG_FAN 0x0010 #define TPACPI_DBG_BRGHT 0x0020 +#define TPACPI_DBG_MIXER 0x0040 #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off") #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled") @@ -256,7 +262,7 @@ struct tp_acpi_drv_struct { struct ibm_struct { char *name; - int (*read) (char *); + int (*read) (struct seq_file *); int (*write) (char *); void (*exit) (void); void (*resume) (void); @@ -298,6 +304,7 @@ static struct { u32 fan_ctrl_status_undef:1; u32 second_fan:1; u32 beep_needs_two_args:1; + u32 mixer_no_level_control:1; u32 input_device_registered:1; u32 platform_drv_registered:1; u32 platform_drv_attrs_registered:1; @@ -309,6 +316,7 @@ static struct { static struct { u16 hotkey_mask_ff:1; + u16 volume_ctrl_forbidden:1; } tp_warned; struct thinkpad_id_data { @@ -425,6 +433,12 @@ static void tpacpi_log_usertask(const char * const what) .ec = TPACPI_MATCH_ANY, \ .quirks = (__quirk) } +#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \ + { .vendor = PCI_VENDOR_ID_LENOVO, \ + .bios = TPACPI_MATCH_ANY, \ + .ec = TPID(__id1, __id2), \ + .quirks = (__quirk) } + struct tpacpi_quirk { unsigned int vendor; u16 bios; @@ -776,36 +790,25 @@ static int __init register_tpacpi_subdriver(struct ibm_struct *ibm) **************************************************************************** ****************************************************************************/ -static int dispatch_procfs_read(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int dispatch_proc_show(struct seq_file *m, void *v) { - struct ibm_struct *ibm = data; - int len; + struct ibm_struct *ibm = m->private; if (!ibm || !ibm->read) return -EINVAL; + return ibm->read(m); +} - len = ibm->read(page); - if (len < 0) - return len; - - if (len <= off + count) - *eof = 1; - *start = page + off; - len -= off; - if (len > count) - len = count; - if (len < 0) - len = 0; - - return len; +static int dispatch_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, dispatch_proc_show, PDE(inode)->data); } -static int dispatch_procfs_write(struct file *file, +static ssize_t dispatch_proc_write(struct file *file, const char __user *userbuf, - unsigned long count, void *data) + size_t count, loff_t *pos) { - struct ibm_struct *ibm = data; + struct ibm_struct *ibm = PDE(file->f_path.dentry->d_inode)->data; char *kernbuf; int ret; @@ -834,6 +837,15 @@ static int dispatch_procfs_write(struct file *file, return ret; } +static const struct file_operations dispatch_proc_fops = { + .owner = THIS_MODULE, + .open = dispatch_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dispatch_proc_write, +}; + static char *next_cmd(char **cmds) { char *start = *cmds; @@ -1261,6 +1273,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, struct tpacpi_rfk *atp_rfk; int res; bool sw_state = false; + bool hw_state; int sw_status; BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]); @@ -1295,7 +1308,8 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, rfkill_init_sw_state(atp_rfk->rfkill, sw_state); } } - rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state()); + hw_state = tpacpi_rfk_check_hwblock_state(); + rfkill_set_hw_state(atp_rfk->rfkill, hw_state); res = rfkill_register(atp_rfk->rfkill); if (res < 0) { @@ -1308,6 +1322,9 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id, } tpacpi_rfkill_switches[id] = atp_rfk; + + printk(TPACPI_INFO "rfkill switch %s: radio is %sblocked\n", + name, (sw_state || hw_state) ? "" : "un"); return 0; } @@ -1380,12 +1397,10 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id, } /* procfs -------------------------------------------------------------- */ -static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) +static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m) { - int len = 0; - if (id >= TPACPI_RFK_SW_MAX) - len += sprintf(p + len, "status:\t\tnot supported\n"); + seq_printf(m, "status:\t\tnot supported\n"); else { int status; @@ -1399,13 +1414,13 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, char *p) return status; } - len += sprintf(p + len, "status:\t\t%s\n", + seq_printf(m, "status:\t\t%s\n", (status == TPACPI_RFK_RADIO_ON) ? "enabled" : "disabled"); - len += sprintf(p + len, "commands:\tenable, disable\n"); + seq_printf(m, "commands:\tenable, disable\n"); } - return len; + return 0; } static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf) @@ -1776,7 +1791,7 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */ TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */ - TPV_QL0('7', 'E', 'D', '0'), /* R60e, R60i */ + TPV_QL1('7', 'E', 'D', '0', '1', '5'), /* R60e, R60i */ /* BIOS FW BIOS VERS EC FW EC VERS */ TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */ @@ -1792,8 +1807,8 @@ static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = { TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */ TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */ - TPV_QL0('7', 'B', 'D', '7'), /* X60/s */ - TPV_QL0('7', 'J', '3', '0'), /* X60t */ + TPV_QL1('7', 'B', 'D', '7', '4', '0'), /* X60/s */ + TPV_QL1('7', 'J', '3', '0', '1', '3'), /* X60t */ /* (0) - older versions lack DMI EC fw string and functionality */ /* (1) - older versions known to lack functionality */ @@ -1883,14 +1898,11 @@ static int __init thinkpad_acpi_driver_init(struct ibm_init_struct *iibm) return 0; } -static int thinkpad_acpi_driver_read(char *p) +static int thinkpad_acpi_driver_read(struct seq_file *m) { - int len = 0; - - len += sprintf(p + len, "driver:\t\t%s\n", TPACPI_DESC); - len += sprintf(p + len, "version:\t%s\n", TPACPI_VERSION); - - return len; + seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC); + seq_printf(m, "version:\t%s\n", TPACPI_VERSION); + return 0; } static struct ibm_struct thinkpad_acpi_driver_data = { @@ -2186,7 +2198,8 @@ static int hotkey_mask_set(u32 mask) fwmask, hotkey_acpi_mask); } - hotkey_mask_warn_incomplete_mask(); + if (tpacpi_lifecycle != TPACPI_LIFE_EXITING) + hotkey_mask_warn_incomplete_mask(); return rc; } @@ -3182,6 +3195,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) int res, i; int status; int hkeyv; + bool radiosw_state = false; + bool tabletsw_state = false; unsigned long quirks; @@ -3287,6 +3302,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES if (dbg_wlswemul) { tp_features.hotkey_wlsw = 1; + radiosw_state = !!tpacpi_wlsw_emulstate; printk(TPACPI_INFO "radio switch emulation enabled\n"); } else @@ -3294,6 +3310,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* Not all thinkpads have a hardware radio switch */ if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) { tp_features.hotkey_wlsw = 1; + radiosw_state = !!status; printk(TPACPI_INFO "radio switch found; radios are %s\n", enabled(status, 0)); @@ -3305,11 +3322,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) /* For X41t, X60t, X61t Tablets... */ if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) { tp_features.hotkey_tablet = 1; + tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK); printk(TPACPI_INFO "possible tablet mode switch found; " "ThinkPad in %s mode\n", - (status & TP_HOTKEY_TABLET_MASK)? - "tablet" : "laptop"); + (tabletsw_state) ? "tablet" : "laptop"); res = add_to_attr_set(hotkey_dev_attributes, &dev_attr_hotkey_tablet_mode.attr); } @@ -3344,16 +3361,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) TPACPI_HOTKEY_MAP_SIZE); } - set_bit(EV_KEY, tpacpi_inputdev->evbit); - set_bit(EV_MSC, tpacpi_inputdev->evbit); - set_bit(MSC_SCAN, tpacpi_inputdev->mscbit); + input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN); tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN; tpacpi_inputdev->keycode = hotkey_keycode_map; for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) { if (hotkey_keycode_map[i] != KEY_RESERVED) { - set_bit(hotkey_keycode_map[i], - tpacpi_inputdev->keybit); + input_set_capability(tpacpi_inputdev, EV_KEY, + hotkey_keycode_map[i]); } else { if (i < sizeof(hotkey_reserved_mask)*8) hotkey_reserved_mask |= 1 << i; @@ -3361,12 +3376,14 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) } if (tp_features.hotkey_wlsw) { - set_bit(EV_SW, tpacpi_inputdev->evbit); - set_bit(SW_RFKILL_ALL, tpacpi_inputdev->swbit); + input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL); + input_report_switch(tpacpi_inputdev, + SW_RFKILL_ALL, radiosw_state); } if (tp_features.hotkey_tablet) { - set_bit(EV_SW, tpacpi_inputdev->evbit); - set_bit(SW_TABLET_MODE, tpacpi_inputdev->swbit); + input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE); + input_report_switch(tpacpi_inputdev, + SW_TABLET_MODE, tabletsw_state); } /* Do not issue duplicate brightness change events to @@ -3433,8 +3450,6 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) tpacpi_inputdev->close = &hotkey_inputdev_close; hotkey_poll_setup_safe(true); - tpacpi_send_radiosw_update(); - tpacpi_input_send_tabletsw(); return 0; @@ -3542,49 +3557,57 @@ static bool hotkey_notify_usrevent(const u32 hkey, } } +static void thermal_dump_all_sensors(void); + static bool hotkey_notify_thermal(const u32 hkey, bool *send_acpi_ev, bool *ignore_acpi_ev) { + bool known = true; + /* 0x6000-0x6FFF: thermal alarms */ *send_acpi_ev = true; *ignore_acpi_ev = false; switch (hkey) { + case TP_HKEY_EV_THM_TABLE_CHANGED: + printk(TPACPI_INFO + "EC reports that Thermal Table has changed\n"); + /* recommended action: do nothing, we don't have + * Lenovo ATM information */ + return true; case TP_HKEY_EV_ALARM_BAT_HOT: printk(TPACPI_CRIT "THERMAL ALARM: battery is too hot!\n"); /* recommended action: warn user through gui */ - return true; + break; case TP_HKEY_EV_ALARM_BAT_XHOT: printk(TPACPI_ALERT "THERMAL EMERGENCY: battery is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ - return true; + break; case TP_HKEY_EV_ALARM_SENSOR_HOT: printk(TPACPI_CRIT "THERMAL ALARM: " "a sensor reports something is too hot!\n"); /* recommended action: warn user through gui, that */ /* some internal component is too hot */ - return true; + break; case TP_HKEY_EV_ALARM_SENSOR_XHOT: printk(TPACPI_ALERT "THERMAL EMERGENCY: " "a sensor reports something is extremely hot!\n"); /* recommended action: immediate sleep/hibernate */ - return true; - case TP_HKEY_EV_THM_TABLE_CHANGED: - printk(TPACPI_INFO - "EC reports that Thermal Table has changed\n"); - /* recommended action: do nothing, we don't have - * Lenovo ATM information */ - return true; + break; default: printk(TPACPI_ALERT "THERMAL ALERT: unknown thermal alarm received\n"); - return false; + known = false; } + + thermal_dump_all_sensors(); + + return known; } static void hotkey_notify(struct ibm_struct *ibm, u32 event) @@ -3727,14 +3750,13 @@ static void hotkey_resume(void) } /* procfs -------------------------------------------------------------- */ -static int hotkey_read(char *p) +static int hotkey_read(struct seq_file *m) { int res, status; - int len = 0; if (!tp_features.hotkey) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; + seq_printf(m, "status:\t\tnot supported\n"); + return 0; } if (mutex_lock_killable(&hotkey_mutex)) @@ -3746,17 +3768,16 @@ static int hotkey_read(char *p) if (res) return res; - len += sprintf(p + len, "status:\t\t%s\n", enabled(status, 0)); + seq_printf(m, "status:\t\t%s\n", enabled(status, 0)); if (hotkey_all_mask) { - len += sprintf(p + len, "mask:\t\t0x%08x\n", hotkey_user_mask); - len += sprintf(p + len, - "commands:\tenable, disable, reset, <mask>\n"); + seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask); + seq_printf(m, "commands:\tenable, disable, reset, <mask>\n"); } else { - len += sprintf(p + len, "mask:\t\tnot supported\n"); - len += sprintf(p + len, "commands:\tenable, disable, reset\n"); + seq_printf(m, "mask:\t\tnot supported\n"); + seq_printf(m, "commands:\tenable, disable, reset\n"); } - return len; + return 0; } static void hotkey_enabledisable_warn(bool enable) @@ -3863,15 +3884,6 @@ enum { #define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw" -static void bluetooth_suspend(pm_message_t state) -{ - /* Try to make sure radio will resume powered off */ - if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd", - TP_ACPI_BLTH_PWR_OFF_ON_RESUME)) - vdbg_printk(TPACPI_DBG_RFKILL, - "bluetooth power down on resume request failed\n"); -} - static int bluetooth_get_status(void) { int status; @@ -3905,10 +3917,9 @@ static int bluetooth_set_status(enum tpacpi_rfkill_state state) #endif /* We make sure to keep TP_ACPI_BLUETOOTH_RESUMECTRL off */ + status = TP_ACPI_BLUETOOTH_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status = TP_ACPI_BLUETOOTH_RADIOSSW; - else - status = 0; + status |= TP_ACPI_BLUETOOTH_RADIOSSW; if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) return -EIO; @@ -4032,9 +4043,9 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) } /* procfs -------------------------------------------------------------- */ -static int bluetooth_read(char *p) +static int bluetooth_read(struct seq_file *m) { - return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, p); + return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m); } static int bluetooth_write(char *buf) @@ -4047,7 +4058,6 @@ static struct ibm_struct bluetooth_driver_data = { .read = bluetooth_read, .write = bluetooth_write, .exit = bluetooth_exit, - .suspend = bluetooth_suspend, .shutdown = bluetooth_shutdown, }; @@ -4065,15 +4075,6 @@ enum { #define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw" -static void wan_suspend(pm_message_t state) -{ - /* Try to make sure radio will resume powered off */ - if (!acpi_evalf(NULL, NULL, "\\WGSV", "qvd", - TP_ACPI_WGSV_PWR_OFF_ON_RESUME)) - vdbg_printk(TPACPI_DBG_RFKILL, - "WWAN power down on resume request failed\n"); -} - static int wan_get_status(void) { int status; @@ -4106,11 +4107,10 @@ static int wan_set_status(enum tpacpi_rfkill_state state) } #endif - /* We make sure to keep TP_ACPI_WANCARD_RESUMECTRL off */ + /* We make sure to set TP_ACPI_WANCARD_RESUMECTRL */ + status = TP_ACPI_WANCARD_RESUMECTRL; if (state == TPACPI_RFK_RADIO_ON) - status = TP_ACPI_WANCARD_RADIOSSW; - else - status = 0; + status |= TP_ACPI_WANCARD_RADIOSSW; if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) return -EIO; @@ -4233,9 +4233,9 @@ static int __init wan_init(struct ibm_init_struct *iibm) } /* procfs -------------------------------------------------------------- */ -static int wan_read(char *p) +static int wan_read(struct seq_file *m) { - return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, p); + return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m); } static int wan_write(char *buf) @@ -4248,7 +4248,6 @@ static struct ibm_struct wan_driver_data = { .read = wan_read, .write = wan_write, .exit = wan_exit, - .suspend = wan_suspend, .shutdown = wan_shutdown, }; @@ -4611,14 +4610,13 @@ static int video_expand_toggle(void) /* not reached */ } -static int video_read(char *p) +static int video_read(struct seq_file *m) { int status, autosw; - int len = 0; if (video_supported == TPACPI_VIDEO_NONE) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; + seq_printf(m, "status:\t\tnot supported\n"); + return 0; } status = video_outputsw_get(); @@ -4629,20 +4627,20 @@ static int video_read(char *p) if (autosw < 0) return autosw; - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "lcd:\t\t%s\n", enabled(status, 0)); - len += sprintf(p + len, "crt:\t\t%s\n", enabled(status, 1)); + seq_printf(m, "status:\t\tsupported\n"); + seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0)); + seq_printf(m, "crt:\t\t%s\n", enabled(status, 1)); if (video_supported == TPACPI_VIDEO_NEW) - len += sprintf(p + len, "dvi:\t\t%s\n", enabled(status, 3)); - len += sprintf(p + len, "auto:\t\t%s\n", enabled(autosw, 0)); - len += sprintf(p + len, "commands:\tlcd_enable, lcd_disable\n"); - len += sprintf(p + len, "commands:\tcrt_enable, crt_disable\n"); + seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3)); + seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0)); + seq_printf(m, "commands:\tlcd_enable, lcd_disable\n"); + seq_printf(m, "commands:\tcrt_enable, crt_disable\n"); if (video_supported == TPACPI_VIDEO_NEW) - len += sprintf(p + len, "commands:\tdvi_enable, dvi_disable\n"); - len += sprintf(p + len, "commands:\tauto_enable, auto_disable\n"); - len += sprintf(p + len, "commands:\tvideo_switch, expand_toggle\n"); + seq_printf(m, "commands:\tdvi_enable, dvi_disable\n"); + seq_printf(m, "commands:\tauto_enable, auto_disable\n"); + seq_printf(m, "commands:\tvideo_switch, expand_toggle\n"); - return len; + return 0; } static int video_write(char *buf) @@ -4834,25 +4832,24 @@ static void light_exit(void) flush_workqueue(tpacpi_wq); } -static int light_read(char *p) +static int light_read(struct seq_file *m) { - int len = 0; int status; if (!tp_features.light) { - len += sprintf(p + len, "status:\t\tnot supported\n"); + seq_printf(m, "status:\t\tnot supported\n"); } else if (!tp_features.light_status) { - len += sprintf(p + len, "status:\t\tunknown\n"); - len += sprintf(p + len, "commands:\ton, off\n"); + seq_printf(m, "status:\t\tunknown\n"); + seq_printf(m, "commands:\ton, off\n"); } else { status = light_get_status(); if (status < 0) return status; - len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); - len += sprintf(p + len, "commands:\ton, off\n"); + seq_printf(m, "status:\t\t%s\n", onoff(status, 0)); + seq_printf(m, "commands:\ton, off\n"); } - return len; + return 0; } static int light_write(char *buf) @@ -4930,20 +4927,18 @@ static void cmos_exit(void) device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command); } -static int cmos_read(char *p) +static int cmos_read(struct seq_file *m) { - int len = 0; - /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, R30, R31, T20-22, X20-21 */ if (!cmos_handle) - len += sprintf(p + len, "status:\t\tnot supported\n"); + seq_printf(m, "status:\t\tnot supported\n"); else { - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-21)\n"); + seq_printf(m, "status:\t\tsupported\n"); + seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n"); } - return len; + return 0; } static int cmos_write(char *buf) @@ -5318,15 +5313,13 @@ static int __init led_init(struct ibm_init_struct *iibm) ((s) == TPACPI_LED_OFF ? "off" : \ ((s) == TPACPI_LED_ON ? "on" : "blinking")) -static int led_read(char *p) +static int led_read(struct seq_file *m) { - int len = 0; - if (!led_supported) { - len += sprintf(p + len, "status:\t\tnot supported\n"); - return len; + seq_printf(m, "status:\t\tnot supported\n"); + return 0; } - len += sprintf(p + len, "status:\t\tsupported\n"); + seq_printf(m, "status:\t\tsupported\n"); if (led_supported == TPACPI_LED_570) { /* 570 */ @@ -5335,15 +5328,15 @@ static int led_read(char *p) status = led_get_status(i); if (status < 0) return -EIO; - len += sprintf(p + len, "%d:\t\t%s\n", + seq_printf(m, "%d:\t\t%s\n", i, str_led_status(status)); } } - len += sprintf(p + len, "commands:\t" + seq_printf(m, "commands:\t" "<led> on, <led> off, <led> blink (<led> is 0-15)\n"); - return len; + return 0; } static int led_write(char *buf) @@ -5416,18 +5409,16 @@ static int __init beep_init(struct ibm_init_struct *iibm) return (beep_handle)? 0 : 1; } -static int beep_read(char *p) +static int beep_read(struct seq_file *m) { - int len = 0; - if (!beep_handle) - len += sprintf(p + len, "status:\t\tnot supported\n"); + seq_printf(m, "status:\t\tnot supported\n"); else { - len += sprintf(p + len, "status:\t\tsupported\n"); - len += sprintf(p + len, "commands:\t<cmd> (<cmd> is 0-17)\n"); + seq_printf(m, "status:\t\tsupported\n"); + seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n"); } - return len; + return 0; } static int beep_write(char *buf) @@ -5480,8 +5471,11 @@ enum { /* TPACPI_THERMAL_TPEC_* */ TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */ TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */ TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */ + + TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */ }; + #define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */ struct ibm_thermal_sensors_struct { s32 temp[TPACPI_MAX_THERMAL_SENSORS]; @@ -5571,6 +5565,28 @@ static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s) return n; } +static void thermal_dump_all_sensors(void) +{ + int n, i; + struct ibm_thermal_sensors_struct t; + + n = thermal_get_sensors(&t); + if (n <= 0) + return; + + printk(TPACPI_NOTICE + "temperatures (Celsius):"); + + for (i = 0; i < n; i++) { + if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA) + printk(KERN_CONT " %d", (int)(t.temp[i] / 1000)); + else + printk(KERN_CONT " N/A"); + } + + printk(KERN_CONT "\n"); +} + /* sysfs temp##_input -------------------------------------------------- */ static ssize_t thermal_temp_input_show(struct device *dev, @@ -5586,7 +5602,7 @@ static ssize_t thermal_temp_input_show(struct device *dev, res = thermal_get_sensor(idx, &value); if (res) return res; - if (value == TP_EC_THERMAL_TMP_NA * 1000) + if (value == TPACPI_THERMAL_SENSOR_NA) return -ENXIO; return snprintf(buf, PAGE_SIZE, "%d\n", value); @@ -5763,9 +5779,8 @@ static void thermal_exit(void) } } -static int thermal_read(char *p) +static int thermal_read(struct seq_file *m) { - int len = 0; int n, i; struct ibm_thermal_sensors_struct t; @@ -5773,16 +5788,16 @@ static int thermal_read(char *p) if (unlikely(n < 0)) return n; - len += sprintf(p + len, "temperatures:\t"); + seq_printf(m, "temperatures:\t"); if (n > 0) { for (i = 0; i < (n - 1); i++) - len += sprintf(p + len, "%d ", t.temp[i] / 1000); - len += sprintf(p + len, "%d\n", t.temp[i] / 1000); + seq_printf(m, "%d ", t.temp[i] / 1000); + seq_printf(m, "%d\n", t.temp[i] / 1000); } else - len += sprintf(p + len, "not supported\n"); + seq_printf(m, "not supported\n"); - return len; + return 0; } static struct ibm_struct thermal_driver_data = { @@ -5797,39 +5812,38 @@ static struct ibm_struct thermal_driver_data = { static u8 ecdump_regs[256]; -static int ecdump_read(char *p) +static int ecdump_read(struct seq_file *m) { - int len = 0; int i, j; u8 v; - len += sprintf(p + len, "EC " + seq_printf(m, "EC " " +00 +01 +02 +03 +04 +05 +06 +07" " +08 +09 +0a +0b +0c +0d +0e +0f\n"); for (i = 0; i < 256; i += 16) { - len += sprintf(p + len, "EC 0x%02x:", i); + seq_printf(m, "EC 0x%02x:", i); for (j = 0; j < 16; j++) { if (!acpi_ec_read(i + j, &v)) break; if (v != ecdump_regs[i + j]) - len += sprintf(p + len, " *%02x", v); + seq_printf(m, " *%02x", v); else - len += sprintf(p + len, " %02x", v); + seq_printf(m, " %02x", v); ecdump_regs[i + j] = v; } - len += sprintf(p + len, "\n"); + seq_putc(m, '\n'); if (j != 16) break; } /* These are way too dangerous to advertise openly... */ #if 0 - len += sprintf(p + len, "commands:\t0x<offset> 0x<value>" + seq_printf(m, "commands:\t0x<offset> 0x<value>" " (<offset> is 00-ff, <value> is 00-ff)\n"); - len += sprintf(p + len, "commands:\t0x<offset> <value> " + seq_printf(m, "commands:\t0x<offset> <value> " " (<offset> is 00-ff, <value> is 0-255)\n"); #endif - return len; + return 0; } static int ecdump_write(char *buf) @@ -6092,6 +6106,12 @@ static int brightness_get(struct backlight_device *bd) return status & TP_EC_BACKLIGHT_LVLMSK; } +static void tpacpi_brightness_notify_change(void) +{ + backlight_force_update(ibm_backlight_device, + BACKLIGHT_UPDATE_HOTKEY); +} + static struct backlight_ops ibm_backlight_data = { .get_brightness = brightness_get, .update_status = brightness_update_status, @@ -6120,8 +6140,8 @@ static const struct tpacpi_quirk brightness_quirk_table[] __initconst = { /* Models with Intel Extreme Graphics 2 */ TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), - TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC), - TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_NOEC), + TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), + TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC), /* Models with Intel GMA900 */ TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */ @@ -6246,6 +6266,12 @@ static int __init brightness_init(struct ibm_init_struct *iibm) ibm_backlight_device->props.brightness = b & TP_EC_BACKLIGHT_LVLMSK; backlight_update_status(ibm_backlight_device); + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT, + "brightness: registering brightness hotkeys " + "as change notification\n"); + tpacpi_hotkey_driver_mask_set(hotkey_driver_mask + | TP_ACPI_HKEY_BRGHTUP_MASK + | TP_ACPI_HKEY_BRGHTDWN_MASK);; return 0; } @@ -6270,23 +6296,22 @@ static void brightness_exit(void) tpacpi_brightness_checkpoint_nvram(); } -static int brightness_read(char *p) +static int brightness_read(struct seq_file *m) { - int len = 0; int level; level = brightness_get(NULL); if (level < 0) { - len += sprintf(p + len, "level:\t\tunreadable\n"); + seq_printf(m, "level:\t\tunreadable\n"); } else { - len += sprintf(p + len, "level:\t\t%d\n", level); - len += sprintf(p + len, "commands:\tup, down\n"); - len += sprintf(p + len, "commands:\tlevel <level>" + seq_printf(m, "level:\t\t%d\n", level); + seq_printf(m, "commands:\tup, down\n"); + seq_printf(m, "commands:\tlevel <level>" " (<level> is 0-%d)\n", (tp_features.bright_16levels) ? 15 : 7); } - return len; + return 0; } static int brightness_write(char *buf) @@ -6322,6 +6347,9 @@ static int brightness_write(char *buf) * Doing it this way makes the syscall restartable in case of EINTR */ rc = brightness_set(level); + if (!rc && ibm_backlight_device) + backlight_force_update(ibm_backlight_device, + BACKLIGHT_UPDATE_SYSFS); return (rc == -EINTR)? -ERESTARTSYS : rc; } @@ -6338,99 +6366,654 @@ static struct ibm_struct brightness_driver_data = { * Volume subdriver */ -static int volume_offset = 0x30; +/* + * IBM ThinkPads have a simple volume controller with MUTE gating. + * Very early Lenovo ThinkPads follow the IBM ThinkPad spec. + * + * Since the *61 series (and probably also the later *60 series), Lenovo + * ThinkPads only implement the MUTE gate. + * + * EC register 0x30 + * Bit 6: MUTE (1 mutes sound) + * Bit 3-0: Volume + * Other bits should be zero as far as we know. + * + * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and + * bits 3-0 (volume). Other bits in NVRAM may have other functions, + * such as bit 7 which is used to detect repeated presses of MUTE, + * and we leave them unchanged. + */ + +#define TPACPI_ALSA_DRVNAME "ThinkPad EC" +#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control" +#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME + +static int alsa_index = SNDRV_DEFAULT_IDX1; +static char *alsa_id = "ThinkPadEC"; +static int alsa_enable = SNDRV_DEFAULT_ENABLE1; + +struct tpacpi_alsa_data { + struct snd_card *card; + struct snd_ctl_elem_id *ctl_mute_id; + struct snd_ctl_elem_id *ctl_vol_id; +}; + +static struct snd_card *alsa_card; + +enum { + TP_EC_AUDIO = 0x30, + + /* TP_EC_AUDIO bits */ + TP_EC_AUDIO_MUTESW = 6, + + /* TP_EC_AUDIO bitmasks */ + TP_EC_AUDIO_LVL_MSK = 0x0F, + TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW), + + /* Maximum volume */ + TP_EC_VOLUME_MAX = 14, +}; + +enum tpacpi_volume_access_mode { + TPACPI_VOL_MODE_AUTO = 0, /* Not implemented yet */ + TPACPI_VOL_MODE_EC, /* Pure EC control */ + TPACPI_VOL_MODE_UCMS_STEP, /* UCMS step-based control: N/A */ + TPACPI_VOL_MODE_ECNVRAM, /* EC control w/ NVRAM store */ + TPACPI_VOL_MODE_MAX +}; + +enum tpacpi_volume_capabilities { + TPACPI_VOL_CAP_AUTO = 0, /* Use white/blacklist */ + TPACPI_VOL_CAP_VOLMUTE, /* Output vol and mute */ + TPACPI_VOL_CAP_MUTEONLY, /* Output mute only */ + TPACPI_VOL_CAP_MAX +}; + +static enum tpacpi_volume_access_mode volume_mode = + TPACPI_VOL_MODE_MAX; + +static enum tpacpi_volume_capabilities volume_capabilities; +static int volume_control_allowed; -static int volume_read(char *p) +/* + * Used to syncronize writers to TP_EC_AUDIO and + * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write + */ +static struct mutex volume_mutex; + +static void tpacpi_volume_checkpoint_nvram(void) { - int len = 0; - u8 level; + u8 lec = 0; + u8 b_nvram; + u8 ec_mask; + + if (volume_mode != TPACPI_VOL_MODE_ECNVRAM) + return; + if (!volume_control_allowed) + return; + + vdbg_printk(TPACPI_DBG_MIXER, + "trying to checkpoint mixer state to NVRAM...\n"); - if (!acpi_ec_read(volume_offset, &level)) { - len += sprintf(p + len, "level:\t\tunreadable\n"); + if (tp_features.mixer_no_level_control) + ec_mask = TP_EC_AUDIO_MUTESW_MSK; + else + ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK; + + if (mutex_lock_killable(&volume_mutex) < 0) + return; + + if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec))) + goto unlock; + lec &= ec_mask; + b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER); + + if (lec != (b_nvram & ec_mask)) { + /* NVRAM needs update */ + b_nvram &= ~ec_mask; + b_nvram |= lec; + nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER); + dbg_printk(TPACPI_DBG_MIXER, + "updated NVRAM mixer status to 0x%02x (0x%02x)\n", + (unsigned int) lec, (unsigned int) b_nvram); } else { - len += sprintf(p + len, "level:\t\t%d\n", level & 0xf); - len += sprintf(p + len, "mute:\t\t%s\n", onoff(level, 6)); - len += sprintf(p + len, "commands:\tup, down, mute\n"); - len += sprintf(p + len, "commands:\tlevel <level>" - " (<level> is 0-15)\n"); + vdbg_printk(TPACPI_DBG_MIXER, + "NVRAM mixer status already is 0x%02x (0x%02x)\n", + (unsigned int) lec, (unsigned int) b_nvram); } - return len; +unlock: + mutex_unlock(&volume_mutex); } -static int volume_write(char *buf) +static int volume_get_status_ec(u8 *status) { - int cmos_cmd, inc, i; - u8 level, mute; - int new_level, new_mute; - char *cmd; + u8 s; - while ((cmd = next_cmd(&buf))) { - if (!acpi_ec_read(volume_offset, &level)) - return -EIO; - new_mute = mute = level & 0x40; - new_level = level = level & 0xf; + if (!acpi_ec_read(TP_EC_AUDIO, &s)) + return -EIO; - if (strlencmp(cmd, "up") == 0) { - if (mute) - new_mute = 0; - else - new_level = level == 15 ? 15 : level + 1; - } else if (strlencmp(cmd, "down") == 0) { - if (mute) - new_mute = 0; - else - new_level = level == 0 ? 0 : level - 1; - } else if (sscanf(cmd, "level %d", &new_level) == 1 && - new_level >= 0 && new_level <= 15) { - /* new_level set */ - } else if (strlencmp(cmd, "mute") == 0) { - new_mute = 0x40; - } else - return -EINVAL; + *status = s; - if (new_level != level) { - /* mute doesn't change */ + dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s); - cmos_cmd = (new_level > level) ? - TP_CMOS_VOLUME_UP : TP_CMOS_VOLUME_DOWN; - inc = new_level > level ? 1 : -1; + return 0; +} - if (mute && (issue_thinkpad_cmos_command(cmos_cmd) || - !acpi_ec_write(volume_offset, level))) - return -EIO; +static int volume_get_status(u8 *status) +{ + return volume_get_status_ec(status); +} - for (i = level; i != new_level; i += inc) - if (issue_thinkpad_cmos_command(cmos_cmd) || - !acpi_ec_write(volume_offset, i + inc)) - return -EIO; +static int volume_set_status_ec(const u8 status) +{ + if (!acpi_ec_write(TP_EC_AUDIO, status)) + return -EIO; - if (mute && - (issue_thinkpad_cmos_command(TP_CMOS_VOLUME_MUTE) || - !acpi_ec_write(volume_offset, new_level + mute))) { - return -EIO; - } + dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status); + + return 0; +} + +static int volume_set_status(const u8 status) +{ + return volume_set_status_ec(status); +} + +static int volume_set_mute_ec(const bool mute) +{ + int rc; + u8 s, n; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + rc = volume_get_status_ec(&s); + if (rc) + goto unlock; + + n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : + s & ~TP_EC_AUDIO_MUTESW_MSK; + + if (n != s) + rc = volume_set_status_ec(n); + +unlock: + mutex_unlock(&volume_mutex); + return rc; +} + +static int volume_set_mute(const bool mute) +{ + dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n", + (mute) ? "" : "un"); + return volume_set_mute_ec(mute); +} + +static int volume_set_volume_ec(const u8 vol) +{ + int rc; + u8 s, n; + + if (vol > TP_EC_VOLUME_MAX) + return -EINVAL; + + if (mutex_lock_killable(&volume_mutex) < 0) + return -EINTR; + + rc = volume_get_status_ec(&s); + if (rc) + goto unlock; + + n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; + + if (n != s) + rc = volume_set_status_ec(n); + +unlock: + mutex_unlock(&volume_mutex); + return rc; +} + +static int volume_set_volume(const u8 vol) +{ + dbg_printk(TPACPI_DBG_MIXER, + "trying to set volume level to %hu\n", vol); + return volume_set_volume_ec(vol); +} + +static void volume_alsa_notify_change(void) +{ + struct tpacpi_alsa_data *d; + + if (alsa_card && alsa_card->private_data) { + d = alsa_card->private_data; + if (d->ctl_mute_id) + snd_ctl_notify(alsa_card, + SNDRV_CTL_EVENT_MASK_VALUE, + d->ctl_mute_id); + if (d->ctl_vol_id) + snd_ctl_notify(alsa_card, + SNDRV_CTL_EVENT_MASK_VALUE, + d->ctl_vol_id); + } +} + +static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = TP_EC_VOLUME_MAX; + return 0; +} + +static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 s; + int rc; + + rc = volume_get_status(&s); + if (rc < 0) + return rc; + + ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK; + return 0; +} + +static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return volume_set_volume(ucontrol->value.integer.value[0]); +} + +#define volume_alsa_mute_info snd_ctl_boolean_mono_info + +static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 s; + int rc; + + rc = volume_get_status(&s); + if (rc < 0) + return rc; + + ucontrol->value.integer.value[0] = + (s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1; + return 0; +} + +static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return volume_set_mute(!ucontrol->value.integer.value[0]); +} + +static struct snd_kcontrol_new volume_alsa_control_vol __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Console Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = volume_alsa_vol_info, + .get = volume_alsa_vol_get, +}; + +static struct snd_kcontrol_new volume_alsa_control_mute __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Console Playback Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = volume_alsa_mute_info, + .get = volume_alsa_mute_get, +}; + +static void volume_suspend(pm_message_t state) +{ + tpacpi_volume_checkpoint_nvram(); +} + +static void volume_resume(void) +{ + volume_alsa_notify_change(); +} + +static void volume_shutdown(void) +{ + tpacpi_volume_checkpoint_nvram(); +} + +static void volume_exit(void) +{ + if (alsa_card) { + snd_card_free(alsa_card); + alsa_card = NULL; + } + + tpacpi_volume_checkpoint_nvram(); +} + +static int __init volume_create_alsa_mixer(void) +{ + struct snd_card *card; + struct tpacpi_alsa_data *data; + struct snd_kcontrol *ctl_vol; + struct snd_kcontrol *ctl_mute; + int rc; + + rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE, + sizeof(struct tpacpi_alsa_data), &card); + if (rc < 0) + return rc; + if (!card) + return -ENOMEM; + + BUG_ON(!card->private_data); + data = card->private_data; + data->card = card; + + strlcpy(card->driver, TPACPI_ALSA_DRVNAME, + sizeof(card->driver)); + strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME, + sizeof(card->shortname)); + snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s", + (thinkpad_id.ec_version_str) ? + thinkpad_id.ec_version_str : "(unknown)"); + snprintf(card->longname, sizeof(card->longname), + "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO, + (thinkpad_id.ec_version_str) ? + thinkpad_id.ec_version_str : "unknown"); + + if (volume_control_allowed) { + volume_alsa_control_vol.put = volume_alsa_vol_put; + volume_alsa_control_vol.access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + + volume_alsa_control_mute.put = volume_alsa_mute_put; + volume_alsa_control_mute.access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + } + + if (!tp_features.mixer_no_level_control) { + ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL); + rc = snd_ctl_add(card, ctl_vol); + if (rc < 0) { + printk(TPACPI_ERR + "Failed to create ALSA volume control\n"); + goto err_out; } + data->ctl_vol_id = &ctl_vol->id; + } - if (new_mute != mute) { - /* level doesn't change */ + ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL); + rc = snd_ctl_add(card, ctl_mute); + if (rc < 0) { + printk(TPACPI_ERR "Failed to create ALSA mute control\n"); + goto err_out; + } + data->ctl_mute_id = &ctl_mute->id; - cmos_cmd = (new_mute) ? - TP_CMOS_VOLUME_MUTE : TP_CMOS_VOLUME_UP; + snd_card_set_dev(card, &tpacpi_pdev->dev); + rc = snd_card_register(card); - if (issue_thinkpad_cmos_command(cmos_cmd) || - !acpi_ec_write(volume_offset, level + new_mute)) - return -EIO; +err_out: + if (rc < 0) { + snd_card_free(card); + card = NULL; + } + + alsa_card = card; + return rc; +} + +#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */ +#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */ + +static const struct tpacpi_quirk volume_quirk_table[] __initconst = { + /* Whitelist volume level on all IBM by default */ + { .vendor = PCI_VENDOR_ID_IBM, + .bios = TPACPI_MATCH_ANY, + .ec = TPACPI_MATCH_ANY, + .quirks = TPACPI_VOL_Q_LEVEL }, + + /* Lenovo models with volume control (needs confirmation) */ + TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */ + TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */ + TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */ + TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */ + TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */ + TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */ + TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */ + + /* Whitelist mute-only on all Lenovo by default */ + { .vendor = PCI_VENDOR_ID_LENOVO, + .bios = TPACPI_MATCH_ANY, + .ec = TPACPI_MATCH_ANY, + .quirks = TPACPI_VOL_Q_MUTEONLY } +}; + +static int __init volume_init(struct ibm_init_struct *iibm) +{ + unsigned long quirks; + int rc; + + vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n"); + + mutex_init(&volume_mutex); + + /* + * Check for module parameter bogosity, note that we + * init volume_mode to TPACPI_VOL_MODE_MAX in order to be + * able to detect "unspecified" + */ + if (volume_mode > TPACPI_VOL_MODE_MAX) + return -EINVAL; + + if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) { + printk(TPACPI_ERR + "UCMS step volume mode not implemented, " + "please contact %s\n", TPACPI_MAIL); + return 1; + } + + if (volume_capabilities >= TPACPI_VOL_CAP_MAX) + return -EINVAL; + + /* + * The ALSA mixer is our primary interface. + * When disabled, don't install the subdriver at all + */ + if (!alsa_enable) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "ALSA mixer disabled by parameter, " + "not loading volume subdriver...\n"); + return 1; + } + + quirks = tpacpi_check_quirks(volume_quirk_table, + ARRAY_SIZE(volume_quirk_table)); + + switch (volume_capabilities) { + case TPACPI_VOL_CAP_AUTO: + if (quirks & TPACPI_VOL_Q_MUTEONLY) + tp_features.mixer_no_level_control = 1; + else if (quirks & TPACPI_VOL_Q_LEVEL) + tp_features.mixer_no_level_control = 0; + else + return 1; /* no mixer */ + break; + case TPACPI_VOL_CAP_VOLMUTE: + tp_features.mixer_no_level_control = 0; + break; + case TPACPI_VOL_CAP_MUTEONLY: + tp_features.mixer_no_level_control = 1; + break; + default: + return 1; + } + + if (volume_capabilities != TPACPI_VOL_CAP_AUTO) + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "using user-supplied volume_capabilities=%d\n", + volume_capabilities); + + if (volume_mode == TPACPI_VOL_MODE_AUTO || + volume_mode == TPACPI_VOL_MODE_MAX) { + volume_mode = TPACPI_VOL_MODE_ECNVRAM; + + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "driver auto-selected volume_mode=%d\n", + volume_mode); + } else { + dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "using user-supplied volume_mode=%d\n", + volume_mode); + } + + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "mute is supported, volume control is %s\n", + str_supported(!tp_features.mixer_no_level_control)); + + rc = volume_create_alsa_mixer(); + if (rc) { + printk(TPACPI_ERR + "Could not create the ALSA mixer interface\n"); + return rc; + } + + printk(TPACPI_INFO + "Console audio control enabled, mode: %s\n", + (volume_control_allowed) ? + "override (read/write)" : + "monitor (read only)"); + + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, + "registering volume hotkeys as change notification\n"); + tpacpi_hotkey_driver_mask_set(hotkey_driver_mask + | TP_ACPI_HKEY_VOLUP_MASK + | TP_ACPI_HKEY_VOLDWN_MASK + | TP_ACPI_HKEY_MUTE_MASK); + + return 0; +} + +static int volume_read(struct seq_file *m) +{ + u8 status; + + if (volume_get_status(&status) < 0) { + seq_printf(m, "level:\t\tunreadable\n"); + } else { + if (tp_features.mixer_no_level_control) + seq_printf(m, "level:\t\tunsupported\n"); + else + seq_printf(m, "level:\t\t%d\n", + status & TP_EC_AUDIO_LVL_MSK); + + seq_printf(m, "mute:\t\t%s\n", + onoff(status, TP_EC_AUDIO_MUTESW)); + + if (volume_control_allowed) { + seq_printf(m, "commands:\tunmute, mute\n"); + if (!tp_features.mixer_no_level_control) { + seq_printf(m, + "commands:\tup, down\n"); + seq_printf(m, + "commands:\tlevel <level>" + " (<level> is 0-%d)\n", + TP_EC_VOLUME_MAX); + } } } return 0; } +static int volume_write(char *buf) +{ + u8 s; + u8 new_level, new_mute; + int l; + char *cmd; + int rc; + + /* + * We do allow volume control at driver startup, so that the + * user can set initial state through the volume=... parameter hack. + */ + if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) { + if (unlikely(!tp_warned.volume_ctrl_forbidden)) { + tp_warned.volume_ctrl_forbidden = 1; + printk(TPACPI_NOTICE + "Console audio control in monitor mode, " + "changes are not allowed.\n"); + printk(TPACPI_NOTICE + "Use the volume_control=1 module parameter " + "to enable volume control\n"); + } + return -EPERM; + } + + rc = volume_get_status(&s); + if (rc < 0) + return rc; + + new_level = s & TP_EC_AUDIO_LVL_MSK; + new_mute = s & TP_EC_AUDIO_MUTESW_MSK; + + while ((cmd = next_cmd(&buf))) { + if (!tp_features.mixer_no_level_control) { + if (strlencmp(cmd, "up") == 0) { + if (new_mute) + new_mute = 0; + else if (new_level < TP_EC_VOLUME_MAX) + new_level++; + continue; + } else if (strlencmp(cmd, "down") == 0) { + if (new_mute) + new_mute = 0; + else if (new_level > 0) + new_level--; + continue; + } else if (sscanf(cmd, "level %u", &l) == 1 && + l >= 0 && l <= TP_EC_VOLUME_MAX) { + new_level = l; + continue; + } + } + if (strlencmp(cmd, "mute") == 0) + new_mute = TP_EC_AUDIO_MUTESW_MSK; + else if (strlencmp(cmd, "unmute") == 0) + new_mute = 0; + else + return -EINVAL; + } + + if (tp_features.mixer_no_level_control) { + tpacpi_disclose_usertask("procfs volume", "%smute\n", + new_mute ? "" : "un"); + rc = volume_set_mute(!!new_mute); + } else { + tpacpi_disclose_usertask("procfs volume", + "%smute and set level to %d\n", + new_mute ? "" : "un", new_level); + rc = volume_set_status(new_mute | new_level); + } + volume_alsa_notify_change(); + + return (rc == -EINTR) ? -ERESTARTSYS : rc; +} + static struct ibm_struct volume_driver_data = { .name = "volume", .read = volume_read, .write = volume_write, + .exit = volume_exit, + .suspend = volume_suspend, + .resume = volume_resume, + .shutdown = volume_shutdown, }; /************************************************************************* @@ -7507,9 +8090,8 @@ static void fan_resume(void) } } -static int fan_read(char *p) +static int fan_read(struct seq_file *m) { - int len = 0; int rc; u8 status; unsigned int speed = 0; @@ -7521,7 +8103,7 @@ static int fan_read(char *p) if (rc < 0) return rc; - len += sprintf(p + len, "status:\t\t%s\n" + seq_printf(m, "status:\t\t%s\n" "level:\t\t%d\n", (status != 0) ? "enabled" : "disabled", status); break; @@ -7532,54 +8114,54 @@ static int fan_read(char *p) if (rc < 0) return rc; - len += sprintf(p + len, "status:\t\t%s\n", + seq_printf(m, "status:\t\t%s\n", (status != 0) ? "enabled" : "disabled"); rc = fan_get_speed(&speed); if (rc < 0) return rc; - len += sprintf(p + len, "speed:\t\t%d\n", speed); + seq_printf(m, "speed:\t\t%d\n", speed); if (status & TP_EC_FAN_FULLSPEED) /* Disengaged mode takes precedence */ - len += sprintf(p + len, "level:\t\tdisengaged\n"); + seq_printf(m, "level:\t\tdisengaged\n"); else if (status & TP_EC_FAN_AUTO) - len += sprintf(p + len, "level:\t\tauto\n"); + seq_printf(m, "level:\t\tauto\n"); else - len += sprintf(p + len, "level:\t\t%d\n", status); + seq_printf(m, "level:\t\t%d\n", status); break; case TPACPI_FAN_NONE: default: - len += sprintf(p + len, "status:\t\tnot supported\n"); + seq_printf(m, "status:\t\tnot supported\n"); } if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) { - len += sprintf(p + len, "commands:\tlevel <level>"); + seq_printf(m, "commands:\tlevel <level>"); switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_SFAN: - len += sprintf(p + len, " (<level> is 0-7)\n"); + seq_printf(m, " (<level> is 0-7)\n"); break; default: - len += sprintf(p + len, " (<level> is 0-7, " + seq_printf(m, " (<level> is 0-7, " "auto, disengaged, full-speed)\n"); break; } } if (fan_control_commands & TPACPI_FAN_CMD_ENABLE) - len += sprintf(p + len, "commands:\tenable, disable\n" + seq_printf(m, "commands:\tenable, disable\n" "commands:\twatchdog <timeout> (<timeout> " "is 0 (off), 1-120 (seconds))\n"); if (fan_control_commands & TPACPI_FAN_CMD_SPEED) - len += sprintf(p + len, "commands:\tspeed <speed>" + seq_printf(m, "commands:\tspeed <speed>" " (<speed> is 0-65535)\n"); - return len; + return 0; } static int fan_write_cmd_level(const char *cmd, int *rc) @@ -7721,10 +8303,23 @@ static struct ibm_struct fan_driver_data = { */ static void tpacpi_driver_event(const unsigned int hkey_event) { + if (ibm_backlight_device) { + switch (hkey_event) { + case TP_HKEY_EV_BRGHT_UP: + case TP_HKEY_EV_BRGHT_DOWN: + tpacpi_brightness_notify_change(); + } + } + if (alsa_card) { + switch (hkey_event) { + case TP_HKEY_EV_VOL_UP: + case TP_HKEY_EV_VOL_DOWN: + case TP_HKEY_EV_VOL_MUTE: + volume_alsa_notify_change(); + } + } } - - static void hotkey_driver_event(const unsigned int scancode) { tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode); @@ -7853,19 +8448,19 @@ static int __init ibm_init(struct ibm_init_struct *iibm) "%s installed\n", ibm->name); if (ibm->read) { - entry = create_proc_entry(ibm->name, - S_IFREG | S_IRUGO | S_IWUSR, - proc_dir); + mode_t mode; + + mode = S_IRUGO; + if (ibm->write) + mode |= S_IWUSR; + entry = proc_create_data(ibm->name, mode, proc_dir, + &dispatch_proc_fops, ibm); if (!entry) { printk(TPACPI_ERR "unable to create proc entry %s\n", ibm->name); ret = -ENODEV; goto err_out; } - entry->data = ibm; - entry->read_proc = &dispatch_procfs_read; - if (ibm->write) - entry->write_proc = &dispatch_procfs_write; ibm->flags.proc_created = 1; } @@ -8077,6 +8672,7 @@ static struct ibm_init_struct ibms_init[] __initdata = { .data = &brightness_driver_data, }, { + .init = volume_init, .data = &volume_driver_data, }, { @@ -8112,36 +8708,59 @@ static int __init set_ibm_param(const char *val, struct kernel_param *kp) return -EINVAL; } -module_param(experimental, int, 0); +module_param(experimental, int, 0444); MODULE_PARM_DESC(experimental, "Enables experimental features when non-zero"); module_param_named(debug, dbg_level, uint, 0); MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); -module_param(force_load, bool, 0); +module_param(force_load, bool, 0444); MODULE_PARM_DESC(force_load, "Attempts to load the driver even on a " "mis-identified ThinkPad when true"); -module_param_named(fan_control, fan_control_allowed, bool, 0); +module_param_named(fan_control, fan_control_allowed, bool, 0444); MODULE_PARM_DESC(fan_control, "Enables setting fan parameters features when true"); -module_param_named(brightness_mode, brightness_mode, uint, 0); +module_param_named(brightness_mode, brightness_mode, uint, 0444); MODULE_PARM_DESC(brightness_mode, "Selects brightness control strategy: " "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM"); -module_param(brightness_enable, uint, 0); +module_param(brightness_enable, uint, 0444); MODULE_PARM_DESC(brightness_enable, "Enables backlight control when 1, disables when 0"); -module_param(hotkey_report_mode, uint, 0); +module_param(hotkey_report_mode, uint, 0444); MODULE_PARM_DESC(hotkey_report_mode, "used for backwards compatibility with userspace, " "see documentation"); +module_param_named(volume_mode, volume_mode, uint, 0444); +MODULE_PARM_DESC(volume_mode, + "Selects volume control strategy: " + "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM"); + +module_param_named(volume_capabilities, volume_capabilities, uint, 0444); +MODULE_PARM_DESC(volume_capabilities, + "Selects the mixer capabilites: " + "0=auto, 1=volume and mute, 2=mute only"); + +module_param_named(volume_control, volume_control_allowed, bool, 0444); +MODULE_PARM_DESC(volume_control, + "Enables software override for the console audio " + "control when true"); + +/* ALSA module API parameters */ +module_param_named(index, alsa_index, int, 0444); +MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer"); +module_param_named(id, alsa_id, charp, 0444); +MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer"); +module_param_named(enable, alsa_enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer"); + #define TPACPI_PARAM(feature) \ module_param_call(feature, set_ibm_param, NULL, NULL, 0); \ MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \ @@ -8160,25 +8779,25 @@ TPACPI_PARAM(volume); TPACPI_PARAM(fan); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES -module_param(dbg_wlswemul, uint, 0); +module_param(dbg_wlswemul, uint, 0444); MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation"); module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0); MODULE_PARM_DESC(wlsw_state, "Initial state of the emulated WLSW switch"); -module_param(dbg_bluetoothemul, uint, 0); +module_param(dbg_bluetoothemul, uint, 0444); MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation"); module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0); MODULE_PARM_DESC(bluetooth_state, "Initial state of the emulated bluetooth switch"); -module_param(dbg_wwanemul, uint, 0); +module_param(dbg_wwanemul, uint, 0444); MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation"); module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0); MODULE_PARM_DESC(wwan_state, "Initial state of the emulated WWAN switch"); -module_param(dbg_uwbemul, uint, 0); +module_param(dbg_uwbemul, uint, 0444); MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation"); module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0); MODULE_PARM_DESC(uwb_state, @@ -8371,6 +8990,7 @@ static int __init thinkpad_acpi_module_init(void) PCI_VENDOR_ID_IBM; tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT; tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION; + tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev; } for (i = 0; i < ARRAY_SIZE(ibms_init); i++) { ret = ibm_init(&ibms_init[i]); diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c new file mode 100644 index 000000000000..a350418e87ea --- /dev/null +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -0,0 +1,144 @@ +/* + * Toshiba Bluetooth Enable Driver + * + * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com> + * + * Thanks to Matthew Garrett for background info on ACPI innards which + * normal people aren't meant to understand :-) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Note the Toshiba Bluetooth RFKill switch seems to be a strange + * fish. It only provides a BT event when the switch is flipped to + * the 'on' position. When flipping it to 'off', the USB device is + * simply pulled away underneath us, without any BT event being + * delivered. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> + +MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>"); +MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); +MODULE_LICENSE("GPL"); + + +static int toshiba_bt_rfkill_add(struct acpi_device *device); +static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type); +static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); +static int toshiba_bt_resume(struct acpi_device *device); + +static const struct acpi_device_id bt_device_ids[] = { + { "TOS6205", 0}, + { "", 0}, +}; +MODULE_DEVICE_TABLE(acpi, bt_device_ids); + +static struct acpi_driver toshiba_bt_rfkill_driver = { + .name = "Toshiba BT", + .class = "Toshiba", + .ids = bt_device_ids, + .ops = { + .add = toshiba_bt_rfkill_add, + .remove = toshiba_bt_rfkill_remove, + .notify = toshiba_bt_rfkill_notify, + .resume = toshiba_bt_resume, + }, + .owner = THIS_MODULE, +}; + + +static int toshiba_bluetooth_enable(acpi_handle handle) +{ + acpi_status res1, res2; + acpi_integer result; + + /* + * Query ACPI to verify RFKill switch is set to 'on'. + * If not, we return silently, no need to report it as + * an error. + */ + res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result); + if (ACPI_FAILURE(res1)) + return res1; + if (!(result & 0x01)) + return 0; + + printk(KERN_INFO "toshiba_bluetooth: Re-enabling Toshiba Bluetooth\n"); + res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL); + res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL); + if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2)) + return 0; + + printk(KERN_WARNING "toshiba_bluetooth: Failed to re-enable " + "Toshiba Bluetooth\n"); + + return -ENODEV; +} + +static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) +{ + toshiba_bluetooth_enable(device->handle); +} + +static int toshiba_bt_resume(struct acpi_device *device) +{ + return toshiba_bluetooth_enable(device->handle); +} + +static int toshiba_bt_rfkill_add(struct acpi_device *device) +{ + acpi_status status; + acpi_integer bt_present; + int result = -ENODEV; + + /* + * Some Toshiba laptops may have a fake TOS6205 device in + * their ACPI BIOS, so query the _STA method to see if there + * is really anything there, before trying to enable it. + */ + status = acpi_evaluate_integer(device->handle, "_STA", NULL, + &bt_present); + + if (!ACPI_FAILURE(status) && bt_present) { + printk(KERN_INFO "Detected Toshiba ACPI Bluetooth device - " + "installing RFKill handler\n"); + result = toshiba_bluetooth_enable(device->handle); + } + + return result; +} + +static int __init toshiba_bt_rfkill_init(void) +{ + int result; + + result = acpi_bus_register_driver(&toshiba_bt_rfkill_driver); + if (result < 0) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error registering driver\n")); + return result; + } + + return 0; +} + +static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type) +{ + /* clean up */ + return 0; +} + +static void __exit toshiba_bt_rfkill_exit(void) +{ + acpi_bus_unregister_driver(&toshiba_bt_rfkill_driver); +} + +module_init(toshiba_bt_rfkill_init); +module_exit(toshiba_bt_rfkill_exit); diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 177f8d767df4..e425a868cd3a 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -30,6 +30,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/device.h> #include <linux/list.h> #include <linux/acpi.h> #include <acpi/acpi_bus.h> @@ -65,6 +66,7 @@ struct wmi_block { acpi_handle handle; wmi_notify_handler handler; void *handler_data; + struct device *dev; }; static struct wmi_block wmi_blocks; @@ -195,6 +197,34 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest) return true; } +/* + * Convert a raw GUID to the ACII string representation + */ +static int wmi_gtoa(const char *in, char *out) +{ + int i; + + for (i = 3; i >= 0; i--) + out += sprintf(out, "%02X", in[i] & 0xFF); + + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[5] & 0xFF); + out += sprintf(out, "%02X", in[4] & 0xFF); + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[7] & 0xFF); + out += sprintf(out, "%02X", in[6] & 0xFF); + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[8] & 0xFF); + out += sprintf(out, "%02X", in[9] & 0xFF); + out += sprintf(out, "-"); + + for (i = 10; i <= 15; i++) + out += sprintf(out, "%02X", in[i] & 0xFF); + + out = '\0'; + return 0; +} + static bool find_guid(const char *guid_string, struct wmi_block **out) { char tmp[16], guid_input[16]; @@ -555,6 +585,138 @@ bool wmi_has_guid(const char *guid_string) EXPORT_SYMBOL_GPL(wmi_has_guid); /* + * sysfs interface + */ +static ssize_t show_modalias(struct device *dev, struct device_attribute *attr, + char *buf) +{ + char guid_string[37]; + struct wmi_block *wblock; + + wblock = dev_get_drvdata(dev); + if (!wblock) + return -ENOMEM; + + wmi_gtoa(wblock->gblock.guid, guid_string); + + return sprintf(buf, "wmi:%s\n", guid_string); +} +static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL); + +static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + char guid_string[37]; + + struct wmi_block *wblock; + + if (add_uevent_var(env, "MODALIAS=")) + return -ENOMEM; + + wblock = dev_get_drvdata(dev); + if (!wblock) + return -ENOMEM; + + wmi_gtoa(wblock->gblock.guid, guid_string); + + strcpy(&env->buf[env->buflen - 1], "wmi:"); + memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36); + env->buflen += 40; + + return 0; +} + +static void wmi_dev_free(struct device *dev) +{ + kfree(dev); +} + +static struct class wmi_class = { + .name = "wmi", + .dev_release = wmi_dev_free, + .dev_uevent = wmi_dev_uevent, +}; + +static int wmi_create_devs(void) +{ + int result; + char guid_string[37]; + struct guid_block *gblock; + struct wmi_block *wblock; + struct list_head *p; + struct device *guid_dev; + + /* Create devices for all the GUIDs */ + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + + guid_dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!guid_dev) + return -ENOMEM; + + wblock->dev = guid_dev; + + guid_dev->class = &wmi_class; + dev_set_drvdata(guid_dev, wblock); + + gblock = &wblock->gblock; + + wmi_gtoa(gblock->guid, guid_string); + dev_set_name(guid_dev, guid_string); + + result = device_register(guid_dev); + if (result) + return result; + + result = device_create_file(guid_dev, &dev_attr_modalias); + if (result) + return result; + } + + return 0; +} + +static void wmi_remove_devs(void) +{ + struct guid_block *gblock; + struct wmi_block *wblock; + struct list_head *p; + struct device *guid_dev; + + /* Delete devices for all the GUIDs */ + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + + guid_dev = wblock->dev; + gblock = &wblock->gblock; + + device_remove_file(guid_dev, &dev_attr_modalias); + + device_unregister(guid_dev); + } +} + +static void wmi_class_exit(void) +{ + wmi_remove_devs(); + class_unregister(&wmi_class); +} + +static int wmi_class_init(void) +{ + int ret; + + ret = class_register(&wmi_class); + if (ret) + return ret; + + ret = wmi_create_devs(); + if (ret) + wmi_class_exit(); + + return ret; +} + +/* * Parse the _WDG method for the GUID data blocks */ static __init acpi_status parse_wdg(acpi_handle handle) @@ -709,10 +871,17 @@ static int __init acpi_wmi_init(void) if (result < 0) { printk(KERN_INFO PREFIX "Error loading mapper\n"); - } else { - printk(KERN_INFO PREFIX "Mapper loaded\n"); + return -ENODEV; + } + + result = wmi_class_init(); + if (result) { + acpi_bus_unregister_driver(&acpi_wmi_driver); + return result; } + printk(KERN_INFO PREFIX "Mapper loaded\n"); + return result; } @@ -721,6 +890,8 @@ static void __exit acpi_wmi_exit(void) struct list_head *p, *tmp; struct wmi_block *wblock; + wmi_class_exit(); + acpi_bus_unregister_driver(&acpi_wmi_driver); list_for_each_safe(p, tmp, &wmi_blocks.list) { |