diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 19 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/atmel_pwm.c | 3 | ||||
-rw-r--r-- | drivers/misc/hp-wmi.c | 494 | ||||
-rw-r--r-- | drivers/misc/phantom.c | 2 | ||||
-rw-r--r-- | drivers/misc/thinkpad_acpi.c | 475 |
6 files changed, 848 insertions, 146 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ce67d973d349..321eb9134635 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -77,11 +77,13 @@ config IBM_ASM for your IBM server. config PHANTOM - tristate "Sensable PHANToM" + tristate "Sensable PHANToM (PCI)" depends on PCI help Say Y here if you want to build a driver for Sensable PHANToM device. + This driver is only for PCI PHANToMs. + If you choose to build module, its name will be phantom. If unsure, say N here. @@ -212,6 +214,18 @@ config TC1100_WMI This is a driver for the WMI extensions (wireless and bluetooth power control) of the HP Compaq TC1100 tablet. +config HP_WMI + tristate "HP WMI extras" + depends on ACPI_WMI + depends on INPUT + depends on RFKILL + help + Say Y here if you want to support WMI-based hotkeys on HP laptops and + to read data from WMI such as docking or ambient light sensor state. + + To compile this driver as a module, choose M here: the module will + be called hp-wmi. + config MSI_LAPTOP tristate "MSI Laptop Extras" depends on X86 @@ -279,6 +293,8 @@ config THINKPAD_ACPI select INPUT select NEW_LEDS select LEDS_CLASS + select NET + select RFKILL ---help--- This is a driver for the IBM and Lenovo ThinkPad laptops. It adds support for Fn-Fx key combinations, Bluetooth control, video @@ -422,6 +438,7 @@ config SGI_XP config HP_ILO tristate "Channel interface driver for HP iLO/iLO2 processor" + depends on PCI default n help The channel interface driver allows applications to communicate diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 688fe76135e0..f5e273420c09 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o +obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c index 5b5a14dab3d3..6aa5294dfec4 100644 --- a/drivers/misc/atmel_pwm.c +++ b/drivers/misc/atmel_pwm.c @@ -211,8 +211,7 @@ int pwm_clk_alloc(unsigned prescale, unsigned div) if ((mr & 0xffff) == 0) { mr |= val; ret = PWM_CPR_CLKA; - } - if ((mr & (0xffff << 16)) == 0) { + } else if ((mr & (0xffff << 16)) == 0) { mr |= val << 16; ret = PWM_CPR_CLKB; } diff --git a/drivers/misc/hp-wmi.c b/drivers/misc/hp-wmi.c new file mode 100644 index 000000000000..1dbcbcb323a2 --- /dev/null +++ b/drivers/misc/hp-wmi.c @@ -0,0 +1,494 @@ +/* + * HP WMI hotkeys + * + * Copyright (C) 2008 Red Hat <mjg@redhat.com> + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/input.h> +#include <acpi/acpi_drivers.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> +#include <linux/rfkill.h> +#include <linux/string.h> + +MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>"); +MODULE_DESCRIPTION("HP laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); +MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); + +#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" +#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" + +#define HPWMI_DISPLAY_QUERY 0x1 +#define HPWMI_HDDTEMP_QUERY 0x2 +#define HPWMI_ALS_QUERY 0x3 +#define HPWMI_DOCK_QUERY 0x4 +#define HPWMI_WIRELESS_QUERY 0x5 + +static int __init hp_wmi_bios_setup(struct platform_device *device); +static int __exit hp_wmi_bios_remove(struct platform_device *device); + +struct bios_args { + u32 signature; + u32 command; + u32 commandtype; + u32 datasize; + u32 data; +}; + +struct bios_return { + u32 sigpass; + u32 return_code; + u32 value; +}; + +struct key_entry { + char type; /* See KE_* below */ + u8 code; + u16 keycode; +}; + +enum { KE_KEY, KE_SW, KE_END }; + +static struct key_entry hp_wmi_keymap[] = { + {KE_SW, 0x01, SW_DOCK}, + {KE_KEY, 0x02, KEY_BRIGHTNESSUP}, + {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN}, + {KE_KEY, 0x04, KEY_HELP}, + {KE_END, 0} +}; + +static struct input_dev *hp_wmi_input_dev; +static struct platform_device *hp_wmi_platform_dev; + +static struct rfkill *wifi_rfkill; +static struct rfkill *bluetooth_rfkill; +static struct rfkill *wwan_rfkill; + +static struct platform_driver hp_wmi_driver = { + .driver = { + .name = "hp-wmi", + .owner = THIS_MODULE, + }, + .probe = hp_wmi_bios_setup, + .remove = hp_wmi_bios_remove, +}; + +static int hp_wmi_perform_query(int query, int write, int value) +{ + struct bios_return bios_return; + acpi_status status; + union acpi_object *obj; + struct bios_args args = { + .signature = 0x55434553, + .command = write ? 0x2 : 0x1, + .commandtype = query, + .datasize = write ? 0x4 : 0, + .data = value, + }; + struct acpi_buffer input = { sizeof(struct bios_args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + + status = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output); + + obj = output.pointer; + + if (!obj || obj->type != ACPI_TYPE_BUFFER) + return -EINVAL; + + bios_return = *((struct bios_return *)obj->buffer.pointer); + if (bios_return.return_code > 0) + return bios_return.return_code * -1; + else + return bios_return.value; +} + +static int hp_wmi_display_state(void) +{ + return hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, 0); +} + +static int hp_wmi_hddtemp_state(void) +{ + return hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, 0); +} + +static int hp_wmi_als_state(void) +{ + return hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, 0); +} + +static int hp_wmi_dock_state(void) +{ + return hp_wmi_perform_query(HPWMI_DOCK_QUERY, 0, 0); +} + +static int hp_wmi_wifi_set(void *data, enum rfkill_state state) +{ + if (state) + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x101); + else + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x100); +} + +static int hp_wmi_bluetooth_set(void *data, enum rfkill_state state) +{ + if (state) + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x202); + else + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x200); +} + +static int hp_wmi_wwan_set(void *data, enum rfkill_state state) +{ + if (state) + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x404); + else + return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, 0x400); +} + +static int hp_wmi_wifi_state(void) +{ + int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + + if (wireless & 0x100) + return 1; + else + return 0; +} + +static int hp_wmi_bluetooth_state(void) +{ + int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + + if (wireless & 0x10000) + return 1; + else + return 0; +} + +static int hp_wmi_wwan_state(void) +{ + int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + + if (wireless & 0x1000000) + return 1; + else + return 0; +} + +static ssize_t show_display(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_display_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_hddtemp_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_als(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_als_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "%d\n", value); +} + +static ssize_t show_dock(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int value = hp_wmi_dock_state(); + if (value < 0) + return -EINVAL; + return sprintf(buf, "%d\n", value); +} + +static ssize_t set_als(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 tmp = simple_strtoul(buf, NULL, 10); + hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, tmp); + return count; +} + +static DEVICE_ATTR(display, S_IRUGO, show_display, NULL); +static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL); +static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als); +static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL); + +static struct key_entry *hp_wmi_get_entry_by_scancode(int code) +{ + struct key_entry *key; + + for (key = hp_wmi_keymap; key->type != KE_END; key++) + if (code == key->code) + return key; + + return NULL; +} + +static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode) +{ + struct key_entry *key; + + for (key = hp_wmi_keymap; key->type != KE_END; key++) + if (key->type == KE_KEY && keycode == key->keycode) + return key; + + return NULL; +} + +static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode); + + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; + } + + return -EINVAL; +} + +static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode) +{ + struct key_entry *key; + int old_keycode; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + key = hp_wmi_get_entry_by_scancode(scancode); + if (key && key->type == KE_KEY) { + old_keycode = key->keycode; + key->keycode = keycode; + set_bit(keycode, dev->keybit); + if (!hp_wmi_get_entry_by_keycode(old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; + } + + return -EINVAL; +} + +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; + + 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); + 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; + case KE_SW: + input_report_switch(hp_wmi_input_dev, + key->keycode, + hp_wmi_dock_state()); + input_sync(hp_wmi_input_dev); + break; + } + } else if (eventcode == 0x5) { + if (wifi_rfkill) + wifi_rfkill->state = hp_wmi_wifi_state(); + if (bluetooth_rfkill) + bluetooth_rfkill->state = + hp_wmi_bluetooth_state(); + if (wwan_rfkill) + wwan_rfkill->state = hp_wmi_wwan_state(); + } else + printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n", + eventcode); + } else + printk(KERN_INFO "HP WMI: Unknown response received\n"); +} + +static int __init hp_wmi_input_setup(void) +{ + struct key_entry *key; + int err; + + hp_wmi_input_dev = input_allocate_device(); + + hp_wmi_input_dev->name = "HP WMI hotkeys"; + hp_wmi_input_dev->phys = "wmi/input0"; + hp_wmi_input_dev->id.bustype = BUS_HOST; + hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode; + hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode; + + for (key = hp_wmi_keymap; key->type != KE_END; key++) { + switch (key->type) { + case KE_KEY: + set_bit(EV_KEY, hp_wmi_input_dev->evbit); + set_bit(key->keycode, hp_wmi_input_dev->keybit); + break; + case KE_SW: + set_bit(EV_SW, hp_wmi_input_dev->evbit); + set_bit(key->keycode, hp_wmi_input_dev->swbit); + break; + } + } + + err = input_register_device(hp_wmi_input_dev); + + if (err) { + input_free_device(hp_wmi_input_dev); + return err; + } + + return 0; +} + +static void cleanup_sysfs(struct platform_device *device) +{ + device_remove_file(&device->dev, &dev_attr_display); + device_remove_file(&device->dev, &dev_attr_hddtemp); + device_remove_file(&device->dev, &dev_attr_als); + device_remove_file(&device->dev, &dev_attr_dock); +} + +static int __init hp_wmi_bios_setup(struct platform_device *device) +{ + int err; + + err = device_create_file(&device->dev, &dev_attr_display); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_hddtemp); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_als); + if (err) + goto add_sysfs_error; + err = device_create_file(&device->dev, &dev_attr_dock); + if (err) + goto add_sysfs_error; + + wifi_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WLAN); + wifi_rfkill->name = "hp-wifi"; + wifi_rfkill->state = hp_wmi_wifi_state(); + wifi_rfkill->toggle_radio = hp_wmi_wifi_set; + wifi_rfkill->user_claim_unsupported = 1; + + bluetooth_rfkill = rfkill_allocate(&device->dev, + RFKILL_TYPE_BLUETOOTH); + bluetooth_rfkill->name = "hp-bluetooth"; + bluetooth_rfkill->state = hp_wmi_bluetooth_state(); + bluetooth_rfkill->toggle_radio = hp_wmi_bluetooth_set; + bluetooth_rfkill->user_claim_unsupported = 1; + + wwan_rfkill = rfkill_allocate(&device->dev, RFKILL_TYPE_WIMAX); + wwan_rfkill->name = "hp-wwan"; + wwan_rfkill->state = hp_wmi_wwan_state(); + wwan_rfkill->toggle_radio = hp_wmi_wwan_set; + wwan_rfkill->user_claim_unsupported = 1; + + rfkill_register(wifi_rfkill); + rfkill_register(bluetooth_rfkill); + rfkill_register(wwan_rfkill); + + return 0; +add_sysfs_error: + cleanup_sysfs(device); + return err; +} + +static int __exit hp_wmi_bios_remove(struct platform_device *device) +{ + cleanup_sysfs(device); + + rfkill_unregister(wifi_rfkill); + rfkill_unregister(bluetooth_rfkill); + rfkill_unregister(wwan_rfkill); + + return 0; +} + +static int __init hp_wmi_init(void) +{ + int err; + + if (wmi_has_guid(HPWMI_EVENT_GUID)) { + err = wmi_install_notify_handler(HPWMI_EVENT_GUID, + hp_wmi_notify, NULL); + if (!err) + hp_wmi_input_setup(); + } + + if (wmi_has_guid(HPWMI_BIOS_GUID)) { + err = platform_driver_register(&hp_wmi_driver); + if (err) + return 0; + hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1); + if (!hp_wmi_platform_dev) { + platform_driver_unregister(&hp_wmi_driver); + return 0; + } + platform_device_add(hp_wmi_platform_dev); + } + + return 0; +} + +static void __exit hp_wmi_exit(void) +{ + if (wmi_has_guid(HPWMI_EVENT_GUID)) { + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + input_unregister_device(hp_wmi_input_dev); + } + if (hp_wmi_platform_dev) { + platform_device_del(hp_wmi_platform_dev); + platform_driver_unregister(&hp_wmi_driver); + } +} + +module_init(hp_wmi_init); +module_exit(hp_wmi_exit); diff --git a/drivers/misc/phantom.c b/drivers/misc/phantom.c index 4ce3bdc2f959..daf585689ce3 100644 --- a/drivers/misc/phantom.c +++ b/drivers/misc/phantom.c @@ -563,6 +563,6 @@ module_init(phantom_init); module_exit(phantom_exit); MODULE_AUTHOR("Jiri Slaby <jirislaby@gmail.com>"); -MODULE_DESCRIPTION("Sensable Phantom driver"); +MODULE_DESCRIPTION("Sensable Phantom driver (PCI devices)"); MODULE_LICENSE("GPL"); MODULE_VERSION(PHANTOM_VERSION); diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index b5969298f3d3..d3eb7903c346 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -21,7 +21,7 @@ * 02110-1301, USA. */ -#define TPACPI_VERSION "0.20" +#define TPACPI_VERSION "0.21" #define TPACPI_SYSFS_VERSION 0x020200 /* @@ -68,6 +68,7 @@ #include <linux/hwmon-sysfs.h> #include <linux/input.h> #include <linux/leds.h> +#include <linux/rfkill.h> #include <asm/uaccess.h> #include <linux/dmi.h> @@ -144,6 +145,12 @@ enum { #define TPACPI_MAX_ACPI_ARGS 3 +/* rfkill switches */ +enum { + TPACPI_RFK_BLUETOOTH_SW_ID = 0, + TPACPI_RFK_WWAN_SW_ID, +}; + /* Debugging */ #define TPACPI_LOG TPACPI_FILE ": " #define TPACPI_ERR KERN_ERR TPACPI_LOG @@ -905,6 +912,43 @@ static int __init tpacpi_check_std_acpi_brightness_support(void) return 0; } +static int __init tpacpi_new_rfkill(const unsigned int id, + struct rfkill **rfk, + const enum rfkill_type rfktype, + const char *name, + int (*toggle_radio)(void *, enum rfkill_state), + int (*get_state)(void *, enum rfkill_state *)) +{ + int res; + enum rfkill_state initial_state; + + *rfk = rfkill_allocate(&tpacpi_pdev->dev, rfktype); + if (!*rfk) { + printk(TPACPI_ERR + "failed to allocate memory for rfkill class\n"); + return -ENOMEM; + } + + (*rfk)->name = name; + (*rfk)->get_state = get_state; + (*rfk)->toggle_radio = toggle_radio; + + if (!get_state(NULL, &initial_state)) + (*rfk)->state = initial_state; + + res = rfkill_register(*rfk); + if (res < 0) { + printk(TPACPI_ERR + "failed to register %s rfkill switch: %d\n", + name, res); + rfkill_free(*rfk); + *rfk = NULL; + return res; + } + + return 0; +} + /************************************************************************* * thinkpad-acpi driver attributes */ @@ -1285,21 +1329,6 @@ static int hotkey_status_set(int status) return 0; } -static void tpacpi_input_send_radiosw(void) -{ - int wlsw; - - if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_report_switch(tpacpi_inputdev, - SW_RFKILL_ALL, !!wlsw); - input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); - } -} - static void tpacpi_input_send_tabletsw(void) { int state; @@ -1921,6 +1950,30 @@ static struct attribute *hotkey_mask_attributes[] __initdata = { &dev_attr_hotkey_wakeup_hotunplug_complete.attr, }; +static void bluetooth_update_rfk(void); +static void wan_update_rfk(void); +static void tpacpi_send_radiosw_update(void) +{ + int wlsw; + + /* Sync these BEFORE sending any rfkill events */ + if (tp_features.bluetooth) + bluetooth_update_rfk(); + if (tp_features.wan) + wan_update_rfk(); + + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&wlsw)) { + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_switch(tpacpi_inputdev, + SW_RFKILL_ALL, !!wlsw); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); + } + hotkey_radio_sw_notify_change(); +} + static void hotkey_exit(void) { #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2167,9 +2220,10 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) printk(TPACPI_INFO "radio switch found; radios are %s\n", enabled(status, 0)); + } + if (tp_features.hotkey_wlsw) res = add_to_attr_set(hotkey_dev_attributes, &dev_attr_hotkey_radio_sw.attr); - } /* For X41t, X60t, X61t Tablets... */ if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) { @@ -2287,7 +2341,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) tpacpi_inputdev->close = &hotkey_inputdev_close; hotkey_poll_setup_safe(1); - tpacpi_input_send_radiosw(); + tpacpi_send_radiosw_update(); tpacpi_input_send_tabletsw(); return 0; @@ -2419,8 +2473,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) case 7: /* 0x7000-0x7FFF: misc */ if (tp_features.hotkey_wlsw && hkey == 0x7000) { - tpacpi_input_send_radiosw(); - hotkey_radio_sw_notify_change(); + tpacpi_send_radiosw_update(); send_acpi_ev = 0; break; } @@ -2463,8 +2516,7 @@ static void hotkey_resume(void) printk(TPACPI_ERR "error while trying to read hot key mask " "from firmware\n"); - tpacpi_input_send_radiosw(); - hotkey_radio_sw_notify_change(); + tpacpi_send_radiosw_update(); hotkey_tablet_mode_notify_change(); hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); @@ -2581,8 +2633,66 @@ enum { TP_ACPI_BLUETOOTH_UNK = 0x04, /* unknown function */ }; -static int bluetooth_get_radiosw(void); -static int bluetooth_set_radiosw(int radio_on); +static struct rfkill *tpacpi_bluetooth_rfkill; + +static int bluetooth_get_radiosw(void) +{ + int status; + + if (!tp_features.bluetooth) + return -ENODEV; + + /* WLSW overrides bluetooth in firmware/hardware, reflect that */ + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) + return RFKILL_STATE_HARD_BLOCKED; + + if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) + return -EIO; + + return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ? + RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +} + +static void bluetooth_update_rfk(void) +{ + int status; + + if (!tpacpi_bluetooth_rfkill) + return; + + status = bluetooth_get_radiosw(); + if (status < 0) + return; + rfkill_force_state(tpacpi_bluetooth_rfkill, status); +} + +static int bluetooth_set_radiosw(int radio_on, int update_rfk) +{ + int status; + + if (!tp_features.bluetooth) + return -ENODEV; + + /* WLSW overrides bluetooth in firmware/hardware, but there is no + * reason to risk weird behaviour. */ + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status + && radio_on) + return -EPERM; + + if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) + return -EIO; + if (radio_on) + status |= TP_ACPI_BLUETOOTH_RADIOSSW; + else + status &= ~TP_ACPI_BLUETOOTH_RADIOSSW; + if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) + return -EIO; + + if (update_rfk) + bluetooth_update_rfk(); + + return 0; +} /* sysfs bluetooth enable ---------------------------------------------- */ static ssize_t bluetooth_enable_show(struct device *dev, @@ -2595,7 +2705,8 @@ static ssize_t bluetooth_enable_show(struct device *dev, if (status < 0) return status; - return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); + return snprintf(buf, PAGE_SIZE, "%d\n", + (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); } static ssize_t bluetooth_enable_store(struct device *dev, @@ -2608,7 +2719,7 @@ static ssize_t bluetooth_enable_store(struct device *dev, if (parse_strtoul(buf, 1, &t)) return -EINVAL; - res = bluetooth_set_radiosw(t); + res = bluetooth_set_radiosw(t, 1); return (res) ? res : count; } @@ -2628,6 +2739,31 @@ static const struct attribute_group bluetooth_attr_group = { .attrs = bluetooth_attributes, }; +static int tpacpi_bluetooth_rfk_get(void *data, enum rfkill_state *state) +{ + int bts = bluetooth_get_radiosw(); + + if (bts < 0) + return bts; + + *state = bts; + return 0; +} + +static int tpacpi_bluetooth_rfk_set(void *data, enum rfkill_state state) +{ + return bluetooth_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); +} + +static void bluetooth_exit(void) +{ + if (tpacpi_bluetooth_rfkill) + rfkill_unregister(tpacpi_bluetooth_rfkill); + + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &bluetooth_attr_group); +} + static int __init bluetooth_init(struct ibm_init_struct *iibm) { int res; @@ -2646,57 +2782,32 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) str_supported(tp_features.bluetooth), status); - if (tp_features.bluetooth) { - if (!(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { - /* no bluetooth hardware present in system */ - tp_features.bluetooth = 0; - dbg_printk(TPACPI_DBG_INIT, - "bluetooth hardware not installed\n"); - } else { - res = sysfs_create_group(&tpacpi_pdev->dev.kobj, - &bluetooth_attr_group); - if (res) - return res; - } + if (tp_features.bluetooth && + !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) { + /* no bluetooth hardware present in system */ + tp_features.bluetooth = 0; + dbg_printk(TPACPI_DBG_INIT, + "bluetooth hardware not installed\n"); } - return (tp_features.bluetooth)? 0 : 1; -} - -static void bluetooth_exit(void) -{ - sysfs_remove_group(&tpacpi_pdev->dev.kobj, - &bluetooth_attr_group); -} - -static int bluetooth_get_radiosw(void) -{ - int status; - if (!tp_features.bluetooth) - return -ENODEV; - - if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) - return -EIO; - - return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0); -} - -static int bluetooth_set_radiosw(int radio_on) -{ - int status; + return 1; - if (!tp_features.bluetooth) - return -ENODEV; + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &bluetooth_attr_group); + if (res) + return res; - if (!acpi_evalf(hkey_handle, &status, "GBDC", "d")) - return -EIO; - if (radio_on) - status |= TP_ACPI_BLUETOOTH_RADIOSSW; - else - status &= ~TP_ACPI_BLUETOOTH_RADIOSSW; - if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status)) - return -EIO; + res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID, + &tpacpi_bluetooth_rfkill, + RFKILL_TYPE_BLUETOOTH, + "tpacpi_bluetooth_sw", + tpacpi_bluetooth_rfk_set, + tpacpi_bluetooth_rfk_get); + if (res) { + bluetooth_exit(); + return res; + } return 0; } @@ -2711,7 +2822,8 @@ static int bluetooth_read(char *p) len += sprintf(p + len, "status:\t\tnot supported\n"); else { len += sprintf(p + len, "status:\t\t%s\n", - (status)? "enabled" : "disabled"); + (status == RFKILL_STATE_UNBLOCKED) ? + "enabled" : "disabled"); len += sprintf(p + len, "commands:\tenable, disable\n"); } @@ -2727,9 +2839,9 @@ static int bluetooth_write(char *buf) while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { - bluetooth_set_radiosw(1); + bluetooth_set_radiosw(1, 1); } else if (strlencmp(cmd, "disable") == 0) { - bluetooth_set_radiosw(0); + bluetooth_set_radiosw(0, 1); } else return -EINVAL; } @@ -2755,8 +2867,66 @@ enum { TP_ACPI_WANCARD_UNK = 0x04, /* unknown function */ }; -static int wan_get_radiosw(void); -static int wan_set_radiosw(int radio_on); +static struct rfkill *tpacpi_wan_rfkill; + +static int wan_get_radiosw(void) +{ + int status; + + if (!tp_features.wan) + return -ENODEV; + + /* WLSW overrides WWAN in firmware/hardware, reflect that */ + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status) + return RFKILL_STATE_HARD_BLOCKED; + + if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) + return -EIO; + + return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ? + RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED; +} + +static void wan_update_rfk(void) +{ + int status; + + if (!tpacpi_wan_rfkill) + return; + + status = wan_get_radiosw(); + if (status < 0) + return; + rfkill_force_state(tpacpi_wan_rfkill, status); +} + +static int wan_set_radiosw(int radio_on, int update_rfk) +{ + int status; + + if (!tp_features.wan) + return -ENODEV; + + /* WLSW overrides bluetooth in firmware/hardware, but there is no + * reason to risk weird behaviour. */ + if (tp_features.hotkey_wlsw && !hotkey_get_wlsw(&status) && !status + && radio_on) + return -EPERM; + + if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) + return -EIO; + if (radio_on) + status |= TP_ACPI_WANCARD_RADIOSSW; + else + status &= ~TP_ACPI_WANCARD_RADIOSSW; + if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) + return -EIO; + + if (update_rfk) + wan_update_rfk(); + + return 0; +} /* sysfs wan enable ---------------------------------------------------- */ static ssize_t wan_enable_show(struct device *dev, @@ -2769,7 +2939,8 @@ static ssize_t wan_enable_show(struct device *dev, if (status < 0) return status; - return snprintf(buf, PAGE_SIZE, "%d\n", status ? 1 : 0); + return snprintf(buf, PAGE_SIZE, "%d\n", + (status == RFKILL_STATE_UNBLOCKED) ? 1 : 0); } static ssize_t wan_enable_store(struct device *dev, @@ -2782,7 +2953,7 @@ static ssize_t wan_enable_store(struct device *dev, if (parse_strtoul(buf, 1, &t)) return -EINVAL; - res = wan_set_radiosw(t); + res = wan_set_radiosw(t, 1); return (res) ? res : count; } @@ -2802,6 +2973,31 @@ static const struct attribute_group wan_attr_group = { .attrs = wan_attributes, }; +static int tpacpi_wan_rfk_get(void *data, enum rfkill_state *state) +{ + int wans = wan_get_radiosw(); + + if (wans < 0) + return wans; + + *state = wans; + return 0; +} + +static int tpacpi_wan_rfk_set(void *data, enum rfkill_state state) +{ + return wan_set_radiosw((state == RFKILL_STATE_UNBLOCKED), 0); +} + +static void wan_exit(void) +{ + if (tpacpi_wan_rfkill) + rfkill_unregister(tpacpi_wan_rfkill); + + sysfs_remove_group(&tpacpi_pdev->dev.kobj, + &wan_attr_group); +} + static int __init wan_init(struct ibm_init_struct *iibm) { int res; @@ -2818,57 +3014,32 @@ static int __init wan_init(struct ibm_init_struct *iibm) str_supported(tp_features.wan), status); - if (tp_features.wan) { - if (!(status & TP_ACPI_WANCARD_HWPRESENT)) { - /* no wan hardware present in system */ - tp_features.wan = 0; - dbg_printk(TPACPI_DBG_INIT, - "wan hardware not installed\n"); - } else { - res = sysfs_create_group(&tpacpi_pdev->dev.kobj, - &wan_attr_group); - if (res) - return res; - } + if (tp_features.wan && + !(status & TP_ACPI_WANCARD_HWPRESENT)) { + /* no wan hardware present in system */ + tp_features.wan = 0; + dbg_printk(TPACPI_DBG_INIT, + "wan hardware not installed\n"); } - return (tp_features.wan)? 0 : 1; -} - -static void wan_exit(void) -{ - sysfs_remove_group(&tpacpi_pdev->dev.kobj, - &wan_attr_group); -} - -static int wan_get_radiosw(void) -{ - int status; - if (!tp_features.wan) - return -ENODEV; - - if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) - return -EIO; - - return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0); -} - -static int wan_set_radiosw(int radio_on) -{ - int status; + return 1; - if (!tp_features.wan) - return -ENODEV; + res = sysfs_create_group(&tpacpi_pdev->dev.kobj, + &wan_attr_group); + if (res) + return res; - if (!acpi_evalf(hkey_handle, &status, "GWAN", "d")) - return -EIO; - if (radio_on) - status |= TP_ACPI_WANCARD_RADIOSSW; - else - status &= ~TP_ACPI_WANCARD_RADIOSSW; - if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status)) - return -EIO; + res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID, + &tpacpi_wan_rfkill, + RFKILL_TYPE_WWAN, + "tpacpi_wwan_sw", + tpacpi_wan_rfk_set, + tpacpi_wan_rfk_get); + if (res) { + wan_exit(); + return res; + } return 0; } @@ -2883,7 +3054,8 @@ static int wan_read(char *p) len += sprintf(p + len, "status:\t\tnot supported\n"); else { len += sprintf(p + len, "status:\t\t%s\n", - (status)? "enabled" : "disabled"); + (status == RFKILL_STATE_UNBLOCKED) ? + "enabled" : "disabled"); len += sprintf(p + len, "commands:\tenable, disable\n"); } @@ -2899,9 +3071,9 @@ static int wan_write(char *buf) while ((cmd = next_cmd(&buf))) { if (strlencmp(cmd, "enable") == 0) { - wan_set_radiosw(1); + wan_set_radiosw(1, 1); } else if (strlencmp(cmd, "disable") == 0) { - wan_set_radiosw(0); + wan_set_radiosw(0, 1); } else return -EINVAL; } @@ -6168,13 +6340,18 @@ err_out: /* Probing */ -static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp) +/* returns 0 - probe ok, or < 0 - probe error. + * Probe ok doesn't mean thinkpad found. + * On error, kfree() cleanup on tp->* is not performed, caller must do it */ +static int __must_check __init get_thinkpad_model_data( + struct thinkpad_id_data *tp) { const struct dmi_device *dev = NULL; char ec_fw_string[18]; + char const *s; if (!tp) - return; + return -EINVAL; memset(tp, 0, sizeof(*tp)); @@ -6183,12 +6360,14 @@ static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp) else if (dmi_name_in_vendors("LENOVO")) tp->vendor = PCI_VENDOR_ID_LENOVO; else - return; + return 0; - tp->bios_version_str = kstrdup(dmi_get_system_info(DMI_BIOS_VERSION), - GFP_KERNEL); + s = dmi_get_system_info(DMI_BIOS_VERSION); + tp->bios_version_str = kstrdup(s, GFP_KERNEL); + if (s && !tp->bios_version_str) + return -ENOMEM; if (!tp->bios_version_str) - return; + return 0; tp->bios_model = tp->bios_version_str[0] | (tp->bios_version_str[1] << 8); @@ -6207,21 +6386,27 @@ static void __init get_thinkpad_model_data(struct thinkpad_id_data *tp) ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); + if (!tp->ec_version_str) + return -ENOMEM; tp->ec_model = ec_fw_string[0] | (ec_fw_string[1] << 8); break; } } - tp->model_str = kstrdup(dmi_get_system_info(DMI_PRODUCT_VERSION), - GFP_KERNEL); - if (tp->model_str && strnicmp(tp->model_str, "ThinkPad", 8) != 0) { - kfree(tp->model_str); - tp->model_str = NULL; + s = dmi_get_system_info(DMI_PRODUCT_VERSION); + if (s && !strnicmp(s, "ThinkPad", 8)) { + tp->model_str = kstrdup(s, GFP_KERNEL); + if (!tp->model_str) + return -ENOMEM; } - tp->nummodel_str = kstrdup(dmi_get_system_info(DMI_PRODUCT_NAME), - GFP_KERNEL); + s = dmi_get_system_info(DMI_PRODUCT_NAME); + tp->nummodel_str = kstrdup(s, GFP_KERNEL); + if (s && !tp->nummodel_str) + return -ENOMEM; + + return 0; } static int __init probe_for_thinkpad(void) @@ -6484,7 +6669,13 @@ static int __init thinkpad_acpi_module_init(void) /* Driver-level probe */ - get_thinkpad_model_data(&thinkpad_id); + ret = get_thinkpad_model_data(&thinkpad_id); + if (ret) { + printk(TPACPI_ERR + "unable to get DMI data: %d\n", ret); + thinkpad_acpi_module_exit(); + return ret; + } ret = probe_for_thinkpad(); if (ret) { thinkpad_acpi_module_exit(); |