diff options
author | Len Brown <len.brown@intel.com> | 2008-02-07 09:19:43 +0100 |
---|---|---|
committer | Len Brown <len.brown@intel.com> | 2008-02-07 09:19:43 +0100 |
commit | 060195500e0347a6ba8ea89739a9898961eb6f2b (patch) | |
tree | 5afba4e8daae642d33b7a4daa97d63c54776b786 /drivers | |
parent | Merge branches 'release' and 'menlo' into release (diff) | |
parent | tc1100-wmi: Add driver for HP Compaq TC1100 Tablets (diff) | |
download | linux-060195500e0347a6ba8ea89739a9898961eb6f2b.tar.xz linux-060195500e0347a6ba8ea89739a9898961eb6f2b.zip |
Merge branches 'release' and 'wmi-2.6.25' into release
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/acpi/Kconfig | 10 | ||||
-rw-r--r-- | drivers/acpi/Makefile | 1 | ||||
-rw-r--r-- | drivers/acpi/wmi.c | 710 | ||||
-rw-r--r-- | drivers/misc/Kconfig | 25 | ||||
-rw-r--r-- | drivers/misc/Makefile | 2 | ||||
-rw-r--r-- | drivers/misc/acer-wmi.c | 1109 | ||||
-rw-r--r-- | drivers/misc/tc1100-wmi.c | 290 |
7 files changed, 2147 insertions, 0 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 558372957fd3..0065f3794576 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -200,6 +200,16 @@ config ACPI_NUMA depends on (X86 || IA64) default y if IA64_GENERIC || IA64_SGI_SN2 +config ACPI_WMI + tristate "WMI (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + This driver adds support for the ACPI-WMI mapper device (PNP0C14) + found on some systems. + + NOTE: You will need another driver or userspace application on top of + this to actually use anything defined in the ACPI-WMI mapper. + config ACPI_ASUS tristate "ASUS/Medion Laptop Extras" depends on X86 diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 456446f90077..f29812a86533 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_ACPI_THERMAL) += thermal.o obj-$(CONFIG_ACPI_SYSTEM) += system.o event.o obj-$(CONFIG_ACPI_DEBUG) += debug.o obj-$(CONFIG_ACPI_NUMA) += numa.o +obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_ACPI_ASUS) += asus_acpi.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c new file mode 100644 index 000000000000..36b84ab418dd --- /dev/null +++ b/drivers/acpi/wmi.c @@ -0,0 +1,710 @@ +/* + * ACPI-WMI mapping driver + * + * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> + * + * GUID parsing code from ldm.c is: + * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> + * Copyright (c) 2001-2007 Anton Altaparmakov + * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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/init.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/acpi.h> +#include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> + +ACPI_MODULE_NAME("wmi"); +MODULE_AUTHOR("Carlos Corbacho"); +MODULE_DESCRIPTION("ACPI-WMI Mapping Driver"); +MODULE_LICENSE("GPL"); + +#define ACPI_WMI_CLASS "wmi" + +#undef PREFIX +#define PREFIX "ACPI: WMI: " + +static DEFINE_MUTEX(wmi_data_lock); + +struct guid_block { + char guid[16]; + union { + char object_id[2]; + struct { + unsigned char notify_id; + unsigned char reserved; + }; + }; + u8 instance_count; + u8 flags; +}; + +struct wmi_block { + struct list_head list; + struct guid_block gblock; + acpi_handle handle; + wmi_notify_handler handler; + void *handler_data; +}; + +static struct wmi_block wmi_blocks; + +/* + * If the GUID data block is marked as expensive, we must enable and + * explicitily disable data collection. + */ +#define ACPI_WMI_EXPENSIVE 0x1 +#define ACPI_WMI_METHOD 0x2 /* GUID is a method */ +#define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */ +#define ACPI_WMI_EVENT 0x8 /* GUID is an event */ + +static int acpi_wmi_remove(struct acpi_device *device, int type); +static int acpi_wmi_add(struct acpi_device *device); + +static const struct acpi_device_id wmi_device_ids[] = { + {"PNP0C14", 0}, + {"pnp0c14", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, wmi_device_ids); + +static struct acpi_driver acpi_wmi_driver = { + .name = "wmi", + .class = ACPI_WMI_CLASS, + .ids = wmi_device_ids, + .ops = { + .add = acpi_wmi_add, + .remove = acpi_wmi_remove, + }, +}; + +/* + * GUID parsing functions + */ + +/** + * wmi_parse_hexbyte - Convert a ASCII hex number to a byte + * @src: Pointer to at least 2 characters to convert. + * + * Convert a two character ASCII hex string to a number. + * + * Return: 0-255 Success, the byte was parsed correctly + * -1 Error, an invalid character was supplied + */ +static int wmi_parse_hexbyte(const u8 *src) +{ + unsigned int x; /* For correct wrapping */ + int h; + + /* high part */ + x = src[0]; + if (x - '0' <= '9' - '0') { + h = x - '0'; + } else if (x - 'a' <= 'f' - 'a') { + h = x - 'a' + 10; + } else if (x - 'A' <= 'F' - 'A') { + h = x - 'A' + 10; + } else { + return -1; + } + h <<= 4; + + /* low part */ + x = src[1]; + if (x - '0' <= '9' - '0') + return h | (x - '0'); + if (x - 'a' <= 'f' - 'a') + return h | (x - 'a' + 10); + if (x - 'A' <= 'F' - 'A') + return h | (x - 'A' + 10); + return -1; +} + +/** + * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary + * @src: Memory block holding binary GUID (16 bytes) + * @dest: Memory block to hold byte swapped binary GUID (16 bytes) + * + * Byte swap a binary GUID to match it's real GUID value + */ +static void wmi_swap_bytes(u8 *src, u8 *dest) +{ + int i; + + for (i = 0; i <= 3; i++) + memcpy(dest + i, src + (3 - i), 1); + + for (i = 0; i <= 1; i++) + memcpy(dest + 4 + i, src + (5 - i), 1); + + for (i = 0; i <= 1; i++) + memcpy(dest + 6 + i, src + (7 - i), 1); + + memcpy(dest + 8, src + 8, 8); +} + +/** + * wmi_parse_guid - Convert GUID from ASCII to binary + * @src: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @dest: Memory block to hold binary GUID (16 bytes) + * + * N.B. The GUID need not be NULL terminated. + * + * Return: 'true' @dest contains binary GUID + * 'false' @dest contents are undefined + */ +static bool wmi_parse_guid(const u8 *src, u8 *dest) +{ + static const int size[] = { 4, 2, 2, 2, 6 }; + int i, j, v; + + if (src[8] != '-' || src[13] != '-' || + src[18] != '-' || src[23] != '-') + return false; + + for (j = 0; j < 5; j++, src++) { + for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) { + v = wmi_parse_hexbyte(src); + if (v < 0) + return false; + } + } + + return true; +} + +static bool find_guid(const char *guid_string, struct wmi_block **out) +{ + char tmp[16], guid_input[16]; + struct wmi_block *wblock; + struct guid_block *block; + struct list_head *p; + + wmi_parse_guid(guid_string, tmp); + wmi_swap_bytes(tmp, guid_input); + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + block = &wblock->gblock; + + if (memcmp(block->guid, guid_input, 16) == 0) { + if (out) + *out = wblock; + return 1; + } + } + return 0; +} + +/* + * Exported WMI functions + */ +/** + * wmi_evaluate_method - Evaluate a WMI method + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * @method_id: Method ID to call + * &in: Buffer containing input for the method call + * &out: Empty buffer to return the method results + * + * Call an ACPI-WMI method + */ +acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, +u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle; + acpi_status status; + struct acpi_object_list input; + union acpi_object params[3]; + char method[4] = "WM"; + + if (!find_guid(guid_string, &wblock)) + return AE_BAD_ADDRESS; + + block = &wblock->gblock; + handle = wblock->handle; + + if (!block->flags & ACPI_WMI_METHOD) + return AE_BAD_DATA; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + input.count = 2; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = method_id; + + if (in) { + input.count = 3; + + if (block->flags & ACPI_WMI_STRING) { + params[2].type = ACPI_TYPE_STRING; + } else { + params[2].type = ACPI_TYPE_BUFFER; + } + params[2].buffer.length = in->length; + params[2].buffer.pointer = in->pointer; + } + + strncat(method, block->object_id, 2); + + status = acpi_evaluate_object(handle, method, &input, out); + + return status; +} +EXPORT_SYMBOL_GPL(wmi_evaluate_method); + +/** + * wmi_query_block - Return contents of a WMI block + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * &out: Empty buffer to return the contents of the data block to + * + * Return the contents of an ACPI-WMI data block to a buffer + */ +acpi_status wmi_query_block(const char *guid_string, u8 instance, +struct acpi_buffer *out) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle; + acpi_status status, wc_status = AE_ERROR; + struct acpi_object_list input, wc_input; + union acpi_object wc_params[1], wq_params[1]; + char method[4]; + char wc_method[4] = "WC"; + + if (!guid_string || !out) + return AE_BAD_PARAMETER; + + if (!find_guid(guid_string, &wblock)) + return AE_BAD_ADDRESS; + + block = &wblock->gblock; + handle = wblock->handle; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_BAD_ADDRESS; + + input.count = 1; + input.pointer = wq_params; + wq_params[0].type = ACPI_TYPE_INTEGER; + wq_params[0].integer.value = instance; + + /* + * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to + * enable collection. + */ + if (block->flags & ACPI_WMI_EXPENSIVE) { + wc_input.count = 1; + wc_input.pointer = wc_params; + wc_params[0].type = ACPI_TYPE_INTEGER; + wc_params[0].integer.value = 1; + + strncat(wc_method, block->object_id, 2); + + /* + * Some GUIDs break the specification by declaring themselves + * expensive, but have no corresponding WCxx method. So we + * should not fail if this happens. + */ + wc_status = acpi_evaluate_object(handle, wc_method, + &wc_input, NULL); + } + + strcpy(method, "WQ"); + strncat(method, block->object_id, 2); + + status = acpi_evaluate_object(handle, method, NULL, out); + + /* + * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if + * the WQxx method failed - we should disable collection anyway. + */ + if ((block->flags & ACPI_WMI_EXPENSIVE) && wc_status) { + wc_params[0].integer.value = 0; + status = acpi_evaluate_object(handle, + wc_method, &wc_input, NULL); + } + + return status; +} +EXPORT_SYMBOL_GPL(wmi_query_block); + +/** + * wmi_set_block - Write to a WMI block + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * @instance: Instance index + * &in: Buffer containing new values for the data block + * + * Write the contents of the input buffer to an ACPI-WMI data block + */ +acpi_status wmi_set_block(const char *guid_string, u8 instance, +const struct acpi_buffer *in) +{ + struct guid_block *block = NULL; + struct wmi_block *wblock = NULL; + acpi_handle handle; + struct acpi_object_list input; + union acpi_object params[2]; + char method[4] = "WS"; + + if (!guid_string || !in) + return AE_BAD_DATA; + + if (!find_guid(guid_string, &wblock)) + return AE_BAD_ADDRESS; + + block = &wblock->gblock; + handle = wblock->handle; + + if (block->instance_count < instance) + return AE_BAD_PARAMETER; + + /* Check GUID is a data block */ + if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) + return AE_BAD_ADDRESS; + + input.count = 2; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = instance; + + if (block->flags & ACPI_WMI_STRING) { + params[1].type = ACPI_TYPE_STRING; + } else { + params[1].type = ACPI_TYPE_BUFFER; + } + params[1].buffer.length = in->length; + params[1].buffer.pointer = in->pointer; + + strncat(method, block->object_id, 2); + + return acpi_evaluate_object(handle, method, &input, NULL); +} +EXPORT_SYMBOL_GPL(wmi_set_block); + +/** + * wmi_install_notify_handler - Register handler for WMI events + * @handler: Function to handle notifications + * @data: Data to be returned to handler when event is fired + * + * Register a handler for events sent to the ACPI-WMI mapper device. + */ +acpi_status wmi_install_notify_handler(const char *guid, +wmi_notify_handler handler, void *data) +{ + struct wmi_block *block; + + if (!guid || !handler) + return AE_BAD_PARAMETER; + + find_guid(guid, &block); + if (!block) + return AE_NOT_EXIST; + + if (block->handler) + return AE_ALREADY_ACQUIRED; + + block->handler = handler; + block->handler_data = data; + + return AE_OK; +} +EXPORT_SYMBOL_GPL(wmi_install_notify_handler); + +/** + * wmi_uninstall_notify_handler - Unregister handler for WMI events + * + * Unregister handler for events sent to the ACPI-WMI mapper device. + */ +acpi_status wmi_remove_notify_handler(const char *guid) +{ + struct wmi_block *block; + + if (!guid) + return AE_BAD_PARAMETER; + + find_guid(guid, &block); + if (!block) + return AE_NOT_EXIST; + + if (!block->handler) + return AE_NULL_ENTRY; + + block->handler = NULL; + block->handler_data = NULL; + + return AE_OK; +} +EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); + +/** + * wmi_get_event_data - Get WMI data associated with an event + * + * @event - Event to find + * &out - Buffer to hold event data + * + * Returns extra data associated with an event in WMI. + */ +acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out) +{ + struct acpi_object_list input; + union acpi_object params[1]; + struct guid_block *gblock; + struct wmi_block *wblock; + struct list_head *p; + + input.count = 1; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = event; + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + gblock = &wblock->gblock; + + if ((gblock->flags & ACPI_WMI_EVENT) && + (gblock->notify_id == event)) + return acpi_evaluate_object(wblock->handle, "_WED", + &input, out); + } + + return AE_NOT_FOUND; +} +EXPORT_SYMBOL_GPL(wmi_get_event_data); + +/** + * wmi_has_guid - Check if a GUID is available + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Check if a given GUID is defined by _WDG + */ +bool wmi_has_guid(const char *guid_string) +{ + return find_guid(guid_string, NULL); +} +EXPORT_SYMBOL_GPL(wmi_has_guid); + +/* + * Parse the _WDG method for the GUID data blocks + */ +static __init acpi_status parse_wdg(acpi_handle handle) +{ + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + struct guid_block *gblock; + struct wmi_block *wblock; + acpi_status status; + u32 i, total; + + status = acpi_evaluate_object(handle, "_WDG", NULL, &out); + + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + + if (obj->type != ACPI_TYPE_BUFFER) + return AE_ERROR; + + total = obj->buffer.length / sizeof(struct guid_block); + + gblock = kzalloc(obj->buffer.length, GFP_KERNEL); + if (!gblock) + return AE_NO_MEMORY; + + memcpy(gblock, obj->buffer.pointer, obj->buffer.length); + + for (i = 0; i < total; i++) { + wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); + if (!wblock) + return AE_NO_MEMORY; + + wblock->gblock = gblock[i]; + wblock->handle = handle; + list_add_tail(&wblock->list, &wmi_blocks.list); + } + + kfree(out.pointer); + kfree(gblock); + + return status; +} + +/* + * WMI can have EmbeddedControl access regions. In which case, we just want to + * hand these off to the EC driver. + */ +static acpi_status +acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address, + u32 bits, acpi_integer * value, + void *handler_context, void *region_context) +{ + int result = 0, i = 0; + u8 temp = 0; + + if ((address > 0xFF) || !value) + return AE_BAD_PARAMETER; + + if (function != ACPI_READ && function != ACPI_WRITE) + return AE_BAD_PARAMETER; + + if (bits != 8) + return AE_BAD_PARAMETER; + + if (function == ACPI_READ) { + result = ec_read(address, &temp); + (*value) |= ((acpi_integer)temp) << i; + } else { + temp = 0xff & ((*value) >> i); + result = ec_write(address, temp); + } + + switch (result) { + case -EINVAL: + return AE_BAD_PARAMETER; + break; + case -ENODEV: + return AE_NOT_FOUND; + break; + case -ETIME: + return AE_TIME; + break; + default: + return AE_OK; + } +} + +static void acpi_wmi_notify(acpi_handle handle, u32 event, void *data) +{ + struct guid_block *block; + struct wmi_block *wblock; + struct list_head *p; + struct acpi_device *device = data; + + list_for_each(p, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + block = &wblock->gblock; + + if ((block->flags & ACPI_WMI_EVENT) && + (block->notify_id == event)) { + if (wblock->handler) + wblock->handler(event, wblock->handler_data); + + acpi_bus_generate_netlink_event( + device->pnp.device_class, device->dev.bus_id, + event, 0); + break; + } + } +} + +static int acpi_wmi_remove(struct acpi_device *device, int type) +{ + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify); + + acpi_remove_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); + + return 0; +} + +static int __init acpi_wmi_add(struct acpi_device *device) +{ + acpi_status status; + int result = 0; + + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_wmi_notify, device); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX "Error installing notify handler\n"); + return -ENODEV; + } + + status = acpi_install_address_space_handler(device->handle, + ACPI_ADR_SPACE_EC, + &acpi_wmi_ec_space_handler, + NULL, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + status = parse_wdg(device->handle); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX "Error installing EC region handler\n"); + return -ENODEV; + } + + return result; +} + +static int __init acpi_wmi_init(void) +{ + acpi_status result; + + if (acpi_disabled) + return -ENODEV; + + INIT_LIST_HEAD(&wmi_blocks.list); + + result = acpi_bus_register_driver(&acpi_wmi_driver); + + if (result < 0) { + printk(KERN_INFO PREFIX "Error loading mapper\n"); + } else { + printk(KERN_INFO PREFIX "Mapper loaded\n"); + } + + return result; +} + +static void __exit acpi_wmi_exit(void) +{ + struct list_head *p, *tmp; + struct wmi_block *wblock; + + acpi_bus_unregister_driver(&acpi_wmi_driver); + + list_for_each_safe(p, tmp, &wmi_blocks.list) { + wblock = list_entry(p, struct wmi_block, list); + + list_del(p); + kfree(wblock); + } + + printk(KERN_INFO PREFIX "Mapper unloaded\n"); +} + +subsys_initcall(acpi_wmi_init); +module_exit(acpi_wmi_exit); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f20c30cf99e0..78cd33861766 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -92,6 +92,22 @@ config TIFM_7XX1 To compile this driver as a module, choose M here: the module will be called tifm_7xx1. +config ACER_WMI + tristate "Acer WMI Laptop Extras (EXPERIMENTAL)" + depends on X86 + depends on EXPERIMENTAL + depends on ACPI + depends on ACPI_WMI + depends on LEDS_CLASS + depends on BACKLIGHT_CLASS_DEVICE + ---help--- + This is a driver for newer Acer (and Wistron) laptops. It adds + wireless radio and bluetooth control, and on some laptops, + exposes the mail LED and LCD backlight. + + If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M + here. + config ASUS_LAPTOP tristate "Asus Laptop Extras (EXPERIMENTAL)" depends on X86 @@ -126,6 +142,15 @@ config FUJITSU_LAPTOP If you have a Fujitsu laptop, say Y or M here. +config TC1100_WMI + tristate "HP Compaq TC1100 Tablet WMI Extras" + depends on X86 && !X86_64 + depends on ACPI + depends on ACPI_WMI + ---help--- + This is a driver for the WMI extensions (wireless and bluetooth power + control) of the HP Compaq TC1100 tablet. + config MSI_LAPTOP tristate "MSI Laptop Extras" depends on X86 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a9e8faffc1b1..1f41654aae4d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -6,8 +6,10 @@ obj- := misc.o # Dummy rule to force built-in.o to be made obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/ obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o +obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o +obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_LKDTM) += lkdtm.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c new file mode 100644 index 000000000000..a4d677504250 --- /dev/null +++ b/drivers/misc/acer-wmi.c @@ -0,0 +1,1109 @@ +/* + * Acer WMI Laptop Extras + * + * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> + * + * Based on acer_acpi: + * Copyright (C) 2005-2007 E.M. Smith + * Copyright (C) 2007-2008 Carlos Corbacho <cathectic@gmail.com> + * + * 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 + */ + +#define ACER_WMI_VERSION "0.1" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/dmi.h> +#include <linux/backlight.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/acpi.h> +#include <linux/i8042.h> + +#include <acpi/acpi_drivers.h> + +MODULE_AUTHOR("Carlos Corbacho"); +MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); +MODULE_LICENSE("GPL"); + +#define ACER_LOGPREFIX "acer-wmi: " +#define ACER_ERR KERN_ERR ACER_LOGPREFIX +#define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX +#define ACER_INFO KERN_INFO ACER_LOGPREFIX + +/* + * The following defines quirks to get some specific functions to work + * which are known to not be supported over ACPI-WMI (such as the mail LED + * on WMID based Acer's) + */ +struct acer_quirks { + const char *vendor; + const char *model; + u16 quirks; +}; + +/* + * Magic Number + * Meaning is unknown - this number is required for writing to ACPI for AMW0 + * (it's also used in acerhk when directly accessing the BIOS) + */ +#define ACER_AMW0_WRITE 0x9610 + +/* + * Bit masks for the AMW0 interface + */ +#define ACER_AMW0_WIRELESS_MASK 0x35 +#define ACER_AMW0_BLUETOOTH_MASK 0x34 +#define ACER_AMW0_MAILLED_MASK 0x31 + +/* + * Method IDs for WMID interface + */ +#define ACER_WMID_GET_WIRELESS_METHODID 1 +#define ACER_WMID_GET_BLUETOOTH_METHODID 2 +#define ACER_WMID_GET_BRIGHTNESS_METHODID 3 +#define ACER_WMID_SET_WIRELESS_METHODID 4 +#define ACER_WMID_SET_BLUETOOTH_METHODID 5 +#define ACER_WMID_SET_BRIGHTNESS_METHODID 6 +#define ACER_WMID_GET_THREEG_METHODID 10 +#define ACER_WMID_SET_THREEG_METHODID 11 + +/* + * Acer ACPI method GUIDs + */ +#define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB" +#define WMID_GUID1 "6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3" +#define WMID_GUID2 "95764E09-FB56-4e83-B31A-37761F60994A" + +MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB"); +MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"); + +/* Temporary workaround until the WMI sysfs interface goes in */ +MODULE_ALIAS("dmi:*:*Acer*:*:"); + +/* + * Interface capability flags + */ +#define ACER_CAP_MAILLED (1<<0) +#define ACER_CAP_WIRELESS (1<<1) +#define ACER_CAP_BLUETOOTH (1<<2) +#define ACER_CAP_BRIGHTNESS (1<<3) +#define ACER_CAP_THREEG (1<<4) +#define ACER_CAP_ANY (0xFFFFFFFF) + +/* + * Interface type flags + */ +enum interface_flags { + ACER_AMW0, + ACER_AMW0_V2, + ACER_WMID, +}; + +#define ACER_DEFAULT_WIRELESS 0 +#define ACER_DEFAULT_BLUETOOTH 0 +#define ACER_DEFAULT_MAILLED 0 +#define ACER_DEFAULT_THREEG 0 + +static int max_brightness = 0xF; + +static int wireless = -1; +static int bluetooth = -1; +static int mailled = -1; +static int brightness = -1; +static int threeg = -1; +static int force_series; + +module_param(mailled, int, 0444); +module_param(wireless, int, 0444); +module_param(bluetooth, int, 0444); +module_param(brightness, int, 0444); +module_param(threeg, int, 0444); +module_param(force_series, int, 0444); +MODULE_PARM_DESC(wireless, "Set initial state of Wireless hardware"); +MODULE_PARM_DESC(bluetooth, "Set initial state of Bluetooth hardware"); +MODULE_PARM_DESC(mailled, "Set initial state of Mail LED"); +MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness"); +MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware"); +MODULE_PARM_DESC(force_series, "Force a different laptop series"); + +struct acer_data { + int mailled; + int wireless; + int bluetooth; + int threeg; + int brightness; +}; + +/* Each low-level interface must define at least some of the following */ +struct wmi_interface { + /* The WMI device type */ + u32 type; + + /* The capabilities this interface provides */ + u32 capability; + + /* Private data for the current interface */ + struct acer_data data; +}; + +/* The static interface pointer, points to the currently detected interface */ +static struct wmi_interface *interface; + +/* + * Embedded Controller quirks + * Some laptops require us to directly access the EC to either enable or query + * features that are not available through WMI. + */ + +struct quirk_entry { + u8 wireless; + u8 mailled; + u8 brightness; + u8 bluetooth; +}; + +static struct quirk_entry *quirks; + +static void set_quirks(void) +{ + if (quirks->mailled) + interface->capability |= ACER_CAP_MAILLED; + + if (quirks->brightness) + interface->capability |= ACER_CAP_BRIGHTNESS; +} + +static int dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 0; +} + +static struct quirk_entry quirk_unknown = { +}; + +static struct quirk_entry quirk_acer_travelmate_2490 = { + .mailled = 1, +}; + +/* This AMW0 laptop has no bluetooth */ +static struct quirk_entry quirk_medion_md_98300 = { + .wireless = 1, +}; + +static struct dmi_system_id acer_quirks[] = { + { + .callback = dmi_matched, + .ident = "Acer Aspire 3100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3100"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 5100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 5630", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 5650", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 5680", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 9110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 2490", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"), + }, + .driver_data = &quirk_acer_travelmate_2490, + }, + { + .callback = dmi_matched, + .ident = "Medion MD 98300", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDION"), + DMI_MATCH(DMI_PRODUCT_NAME, "WAM2030"), + }, + .driver_data = &quirk_medion_md_98300, + }, + {} +}; + +/* Find which quirks are needed for a particular vendor/ model pair */ +static void find_quirks(void) +{ + if (!force_series) { + dmi_check_system(acer_quirks); + } else if (force_series == 2490) { + quirks = &quirk_acer_travelmate_2490; + } + + if (quirks == NULL) + quirks = &quirk_unknown; + + set_quirks(); +} + +/* + * General interface convenience methods + */ + +static bool has_cap(u32 cap) +{ + if ((interface->capability & cap) != 0) + return 1; + + return 0; +} + +/* + * AMW0 (V1) interface + */ +struct wmab_args { + u32 eax; + u32 ebx; + u32 ecx; + u32 edx; +}; + +struct wmab_ret { + u32 eax; + u32 ebx; + u32 ecx; + u32 edx; + u32 eex; +}; + +static acpi_status wmab_execute(struct wmab_args *regbuf, +struct acpi_buffer *result) +{ + struct acpi_buffer input; + acpi_status status; + input.length = sizeof(struct wmab_args); + input.pointer = (u8 *)regbuf; + + status = wmi_evaluate_method(AMW0_GUID1, 1, 1, &input, result); + + return status; +} + +static acpi_status AMW0_get_u32(u32 *value, u32 cap, +struct wmi_interface *iface) +{ + int err; + u8 result; + + switch (cap) { + case ACER_CAP_MAILLED: + switch (quirks->mailled) { + default: + err = ec_read(0xA, &result); + if (err) + return AE_ERROR; + *value = (result >> 7) & 0x1; + return AE_OK; + } + break; + case ACER_CAP_WIRELESS: + switch (quirks->wireless) { + case 1: + err = ec_read(0x7B, &result); + if (err) + return AE_ERROR; + *value = result & 0x1; + return AE_OK; + default: + err = ec_read(0xA, &result); + if (err) + return AE_ERROR; + *value = (result >> 2) & 0x1; + return AE_OK; + } + break; + case ACER_CAP_BLUETOOTH: + switch (quirks->bluetooth) { + default: + err = ec_read(0xA, &result); + if (err) + return AE_ERROR; + *value = (result >> 4) & 0x1; + return AE_OK; + } + break; + case ACER_CAP_BRIGHTNESS: + switch (quirks->brightness) { + default: + err = ec_read(0x83, &result); + if (err) + return AE_ERROR; + *value = result; + return AE_OK; + } + break; + default: + return AE_BAD_ADDRESS; + } + return AE_OK; +} + +static acpi_status AMW0_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +{ + struct wmab_args args; + + args.eax = ACER_AMW0_WRITE; + args.ebx = value ? (1<<8) : 0; + args.ecx = args.edx = 0; + + switch (cap) { + case ACER_CAP_MAILLED: + if (value > 1) + return AE_BAD_PARAMETER; + args.ebx |= ACER_AMW0_MAILLED_MASK; + break; + case ACER_CAP_WIRELESS: + if (value > 1) + return AE_BAD_PARAMETER; + args.ebx |= ACER_AMW0_WIRELESS_MASK; + break; + case ACER_CAP_BLUETOOTH: + if (value > 1) + return AE_BAD_PARAMETER; + args.ebx |= ACER_AMW0_BLUETOOTH_MASK; + break; + case ACER_CAP_BRIGHTNESS: + if (value > max_brightness) + return AE_BAD_PARAMETER; + switch (quirks->brightness) { + case 1: + return ec_write(0x83, value); + default: + return AE_BAD_ADDRESS; + break; + } + default: + return AE_BAD_ADDRESS; + } + + /* Actually do the set */ + return wmab_execute(&args, NULL); +} + +static acpi_status AMW0_find_mailled(void) +{ + struct wmab_args args; + struct wmab_ret ret; + acpi_status status = AE_OK; + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + + args.eax = 0x86; + args.ebx = args.ecx = args.edx = 0; + + status = wmab_execute(&args, &out); + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length == sizeof(struct wmab_ret)) { + ret = *((struct wmab_ret *) obj->buffer.pointer); + } else { + return AE_ERROR; + } + + if (ret.eex & 0x1) + interface->capability |= ACER_CAP_MAILLED; + + kfree(out.pointer); + + return AE_OK; +} + +static acpi_status AMW0_set_capabilities(void) +{ + struct wmab_args args; + struct wmab_ret ret; + acpi_status status = AE_OK; + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + + args.eax = ACER_AMW0_WRITE; + args.ecx = args.edx = 0; + + args.ebx = 0xa2 << 8; + args.ebx |= ACER_AMW0_WIRELESS_MASK; + + status = wmab_execute(&args, &out); + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length == sizeof(struct wmab_ret)) { + ret = *((struct wmab_ret *) obj->buffer.pointer); + } else { + return AE_ERROR; + } + + if (ret.eax & 0x1) + interface->capability |= ACER_CAP_WIRELESS; + + args.ebx = 2 << 8; + args.ebx |= ACER_AMW0_BLUETOOTH_MASK; + + status = wmab_execute(&args, &out); + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER + && obj->buffer.length == sizeof(struct wmab_ret)) { + ret = *((struct wmab_ret *) obj->buffer.pointer); + } else { + return AE_ERROR; + } + + if (ret.eax & 0x1) + interface->capability |= ACER_CAP_BLUETOOTH; + + kfree(out.pointer); + + /* + * This appears to be safe to enable, since all Wistron based laptops + * appear to use the same EC register for brightness, even if they + * differ for wireless, etc + */ + interface->capability |= ACER_CAP_BRIGHTNESS; + + return AE_OK; +} + +static struct wmi_interface AMW0_interface = { + .type = ACER_AMW0, +}; + +static struct wmi_interface AMW0_V2_interface = { + .type = ACER_AMW0_V2, +}; + +/* + * New interface (The WMID interface) + */ +static acpi_status +WMI_execute_u32(u32 method_id, u32 in, u32 *out) +{ + struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) }; + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + u32 tmp; + acpi_status status; + + status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result); + + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) result.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length == sizeof(u32)) { + tmp = *((u32 *) obj->buffer.pointer); + } else { + tmp = 0; + } + + if (out) + *out = tmp; + + kfree(result.pointer); + + return status; +} + +static acpi_status WMID_get_u32(u32 *value, u32 cap, +struct wmi_interface *iface) +{ + acpi_status status; + u8 tmp; + u32 result, method_id = 0; + + switch (cap) { + case ACER_CAP_WIRELESS: + method_id = ACER_WMID_GET_WIRELESS_METHODID; + break; + case ACER_CAP_BLUETOOTH: + method_id = ACER_WMID_GET_BLUETOOTH_METHODID; + break; + case ACER_CAP_BRIGHTNESS: + method_id = ACER_WMID_GET_BRIGHTNESS_METHODID; + break; + case ACER_CAP_THREEG: + method_id = ACER_WMID_GET_THREEG_METHODID; + break; + case ACER_CAP_MAILLED: + if (quirks->mailled == 1) { + ec_read(0x9f, &tmp); + *value = tmp & 0x1; + return 0; + } + default: + return AE_BAD_ADDRESS; + } + status = WMI_execute_u32(method_id, 0, &result); + + if (ACPI_SUCCESS(status)) + *value = (u8)result; + + return status; +} + +static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface) +{ + u32 method_id = 0; + char param; + + switch (cap) { + case ACER_CAP_BRIGHTNESS: + if (value > max_brightness) + return AE_BAD_PARAMETER; + method_id = ACER_WMID_SET_BRIGHTNESS_METHODID; + break; + case ACER_CAP_WIRELESS: + if (value > 1) + return AE_BAD_PARAMETER; + method_id = ACER_WMID_SET_WIRELESS_METHODID; + break; + case ACER_CAP_BLUETOOTH: + if (value > 1) + return AE_BAD_PARAMETER; + method_id = ACER_WMID_SET_BLUETOOTH_METHODID; + break; + case ACER_CAP_THREEG: + if (value > 1) + return AE_BAD_PARAMETER; + method_id = ACER_WMID_SET_THREEG_METHODID; + break; + case ACER_CAP_MAILLED: + if (value > 1) + return AE_BAD_PARAMETER; + if (quirks->mailled == 1) { + param = value ? 0x92 : 0x93; + i8042_command(¶m, 0x1059); + return 0; + } + break; + default: + return AE_BAD_ADDRESS; + } + return WMI_execute_u32(method_id, (u32)value, NULL); +} + +static acpi_status WMID_set_capabilities(void) +{ + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + acpi_status status; + u32 devices; + + status = wmi_query_block(WMID_GUID2, 1, &out); + if (ACPI_FAILURE(status)) + return status; + + obj = (union acpi_object *) out.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length == sizeof(u32)) { + devices = *((u32 *) obj->buffer.pointer); + } else { + return AE_ERROR; + } + + /* Not sure on the meaning of the relevant bits yet to detect these */ + interface->capability |= ACER_CAP_WIRELESS; + interface->capability |= ACER_CAP_THREEG; + + /* WMID always provides brightness methods */ + interface->capability |= ACER_CAP_BRIGHTNESS; + + if (devices & 0x10) + interface->capability |= ACER_CAP_BLUETOOTH; + + if (!(devices & 0x20)) + max_brightness = 0x9; + + return status; +} + +static struct wmi_interface wmid_interface = { + .type = ACER_WMID, +}; + +/* + * Generic Device (interface-independent) + */ + +static acpi_status get_u32(u32 *value, u32 cap) +{ + acpi_status status = AE_BAD_ADDRESS; + + switch (interface->type) { + case ACER_AMW0: + status = AMW0_get_u32(value, cap, interface); + break; + case ACER_AMW0_V2: + if (cap == ACER_CAP_MAILLED) { + status = AMW0_get_u32(value, cap, interface); + break; + } + case ACER_WMID: + status = WMID_get_u32(value, cap, interface); + break; + } + + return status; +} + +static acpi_status set_u32(u32 value, u32 cap) +{ + if (interface->capability & cap) { + switch (interface->type) { + case ACER_AMW0: + return AMW0_set_u32(value, cap, interface); + case ACER_AMW0_V2: + case ACER_WMID: + return WMID_set_u32(value, cap, interface); + default: + return AE_BAD_PARAMETER; + } + } + return AE_BAD_PARAMETER; +} + +static void __init acer_commandline_init(void) +{ + /* + * These will all fail silently if the value given is invalid, or the + * capability isn't available on the given interface + */ + set_u32(mailled, ACER_CAP_MAILLED); + set_u32(wireless, ACER_CAP_WIRELESS); + set_u32(bluetooth, ACER_CAP_BLUETOOTH); + set_u32(threeg, ACER_CAP_THREEG); + set_u32(brightness, ACER_CAP_BRIGHTNESS); +} + +/* + * LED device (Mail LED only, no other LEDs known yet) + */ +static void mail_led_set(struct led_classdev *led_cdev, +enum led_brightness value) +{ + set_u32(value, ACER_CAP_MAILLED); +} + +static struct led_classdev mail_led = { + .name = "acer-mail:green", + .brightness_set = mail_led_set, +}; + +static int __init acer_led_init(struct device *dev) +{ + return led_classdev_register(dev, &mail_led); +} + +static void acer_led_exit(void) +{ + led_classdev_unregister(&mail_led); +} + +/* + * Backlight device + */ +static struct backlight_device *acer_backlight_device; + +static int read_brightness(struct backlight_device *bd) +{ + u32 value; + get_u32(&value, ACER_CAP_BRIGHTNESS); + return value; +} + +static int update_bl_status(struct backlight_device *bd) +{ + set_u32(bd->props.brightness, ACER_CAP_BRIGHTNESS); + return 0; +} + +static struct backlight_ops acer_bl_ops = { + .get_brightness = read_brightness, + .update_status = update_bl_status, +}; + +static int __init acer_backlight_init(struct device *dev) +{ + struct backlight_device *bd; + + bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops); + if (IS_ERR(bd)) { + printk(ACER_ERR "Could not register Acer backlight device\n"); + acer_backlight_device = NULL; + return PTR_ERR(bd); + } + + acer_backlight_device = bd; + + bd->props.max_brightness = max_brightness; + bd->props.brightness = read_brightness(NULL); + backlight_update_status(bd); + return 0; +} + +static void __exit acer_backlight_exit(void) +{ + backlight_device_unregister(acer_backlight_device); +} + +/* + * Read/ write bool sysfs macro + */ +#define show_set_bool(value, cap) \ +static ssize_t \ +show_bool_##value(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + u32 result; \ + acpi_status status = get_u32(&result, cap); \ + if (ACPI_SUCCESS(status)) \ + return sprintf(buf, "%u\n", result); \ + return sprintf(buf, "Read error\n"); \ +} \ +\ +static ssize_t \ +set_bool_##value(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + u32 tmp = simple_strtoul(buf, NULL, 10); \ + acpi_status status = set_u32(tmp, cap); \ + if (ACPI_FAILURE(status)) \ + return -EINVAL; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \ + show_bool_##value, set_bool_##value); + +show_set_bool(wireless, ACER_CAP_WIRELESS); +show_set_bool(bluetooth, ACER_CAP_BLUETOOTH); +show_set_bool(threeg, ACER_CAP_THREEG); + +/* + * Read interface sysfs macro + */ +static ssize_t show_interface(struct device *dev, struct device_attribute *attr, + char *buf) +{ + switch (interface->type) { + case ACER_AMW0: + return sprintf(buf, "AMW0\n"); + case ACER_AMW0_V2: + return sprintf(buf, "AMW0 v2\n"); + case ACER_WMID: + return sprintf(buf, "WMID\n"); + default: + return sprintf(buf, "Error!\n"); + } +} + +static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR, + show_interface, NULL); + +/* + * Platform device + */ +static int __devinit acer_platform_probe(struct platform_device *device) +{ + int err; + + if (has_cap(ACER_CAP_MAILLED)) { + err = acer_led_init(&device->dev); + if (err) + goto error_mailled; + } + + if (has_cap(ACER_CAP_BRIGHTNESS)) { + err = acer_backlight_init(&device->dev); + if (err) + goto error_brightness; + } + + return 0; + +error_brightness: + acer_led_exit(); +error_mailled: + return err; +} + +static int acer_platform_remove(struct platform_device *device) +{ + if (has_cap(ACER_CAP_MAILLED)) + acer_led_exit(); + if (has_cap(ACER_CAP_BRIGHTNESS)) + acer_backlight_exit(); + return 0; +} + +static int acer_platform_suspend(struct platform_device *dev, +pm_message_t state) +{ + u32 value; + struct acer_data *data = &interface->data; + + if (!data) + return -ENOMEM; + + if (has_cap(ACER_CAP_WIRELESS)) { + get_u32(&value, ACER_CAP_WIRELESS); + data->wireless = value; + } + + if (has_cap(ACER_CAP_BLUETOOTH)) { + get_u32(&value, ACER_CAP_BLUETOOTH); + data->bluetooth = value; + } + + if (has_cap(ACER_CAP_MAILLED)) { + get_u32(&value, ACER_CAP_MAILLED); + data->mailled = value; + } + + if (has_cap(ACER_CAP_BRIGHTNESS)) { + get_u32(&value, ACER_CAP_BRIGHTNESS); + data->brightness = value; + } + + return 0; +} + +static int acer_platform_resume(struct platform_device *device) +{ + struct acer_data *data = &interface->data; + + if (!data) + return -ENOMEM; + + if (has_cap(ACER_CAP_WIRELESS)) + set_u32(data->wireless, ACER_CAP_WIRELESS); + + if (has_cap(ACER_CAP_BLUETOOTH)) + set_u32(data->bluetooth, ACER_CAP_BLUETOOTH); + + if (has_cap(ACER_CAP_THREEG)) + set_u32(data->threeg, ACER_CAP_THREEG); + + if (has_cap(ACER_CAP_MAILLED)) + set_u32(data->mailled, ACER_CAP_MAILLED); + + if (has_cap(ACER_CAP_BRIGHTNESS)) + set_u32(data->brightness, ACER_CAP_BRIGHTNESS); + + return 0; +} + +static struct platform_driver acer_platform_driver = { + .driver = { + .name = "acer-wmi", + .owner = THIS_MODULE, + }, + .probe = acer_platform_probe, + .remove = acer_platform_remove, + .suspend = acer_platform_suspend, + .resume = acer_platform_resume, +}; + +static struct platform_device *acer_platform_device; + +static int remove_sysfs(struct platform_device *device) +{ + if (has_cap(ACER_CAP_WIRELESS)) + device_remove_file(&device->dev, &dev_attr_wireless); + + if (has_cap(ACER_CAP_BLUETOOTH)) + device_remove_file(&device->dev, &dev_attr_bluetooth); + + if (has_cap(ACER_CAP_THREEG)) + device_remove_file(&device->dev, &dev_attr_threeg); + + device_remove_file(&device->dev, &dev_attr_interface); + + return 0; +} + +static int create_sysfs(void) +{ + int retval = -ENOMEM; + + if (has_cap(ACER_CAP_WIRELESS)) { + retval = device_create_file(&acer_platform_device->dev, + &dev_attr_wireless); + if (retval) + goto error_sysfs; + } + + if (has_cap(ACER_CAP_BLUETOOTH)) { + retval = device_create_file(&acer_platform_device->dev, + &dev_attr_bluetooth); + if (retval) + goto error_sysfs; + } + + if (has_cap(ACER_CAP_THREEG)) { + retval = device_create_file(&acer_platform_device->dev, + &dev_attr_threeg); + if (retval) + goto error_sysfs; + } + + retval = device_create_file(&acer_platform_device->dev, + &dev_attr_interface); + if (retval) + goto error_sysfs; + + return 0; + +error_sysfs: + remove_sysfs(acer_platform_device); + return retval; +} + +static int __init acer_wmi_init(void) +{ + int err; + + printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n", + ACER_WMI_VERSION); + + /* + * Detect which ACPI-WMI interface we're using. + */ + if (wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1)) + interface = &AMW0_V2_interface; + + if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1)) + interface = &wmid_interface; + + if (wmi_has_guid(WMID_GUID2) && interface) { + if (ACPI_FAILURE(WMID_set_capabilities())) { + printk(ACER_ERR "Unable to detect available devices\n"); + return -ENODEV; + } + } else if (!wmi_has_guid(WMID_GUID2) && interface) { + printk(ACER_ERR "Unable to detect available devices\n"); + return -ENODEV; + } + + if (wmi_has_guid(AMW0_GUID1) && !wmi_has_guid(WMID_GUID1)) { + interface = &AMW0_interface; + + if (ACPI_FAILURE(AMW0_set_capabilities())) { + printk(ACER_ERR "Unable to detect available devices\n"); + return -ENODEV; + } + } + + if (wmi_has_guid(AMW0_GUID1)) { + if (ACPI_FAILURE(AMW0_find_mailled())) + printk(ACER_ERR "Unable to detect mail LED\n"); + } + + find_quirks(); + + if (!interface) { + printk(ACER_ERR "No or unsupported WMI interface, unable to "); + printk(KERN_CONT "load.\n"); + return -ENODEV; + } + + if (platform_driver_register(&acer_platform_driver)) { + printk(ACER_ERR "Unable to register platform driver.\n"); + goto error_platform_register; + } + acer_platform_device = platform_device_alloc("acer-wmi", -1); + platform_device_add(acer_platform_device); + + err = create_sysfs(); + if (err) + return err; + + /* Override any initial settings with values from the commandline */ + acer_commandline_init(); + + return 0; + +error_platform_register: + return -ENODEV; +} + +static void __exit acer_wmi_exit(void) +{ + remove_sysfs(acer_platform_device); + platform_device_del(acer_platform_device); + platform_driver_unregister(&acer_platform_driver); + + printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n"); + return; +} + +module_init(acer_wmi_init); +module_exit(acer_wmi_exit); diff --git a/drivers/misc/tc1100-wmi.c b/drivers/misc/tc1100-wmi.c new file mode 100644 index 000000000000..f25e4c974dcf --- /dev/null +++ b/drivers/misc/tc1100-wmi.c @@ -0,0 +1,290 @@ +/* + * HP Compaq TC1100 Tablet WMI Extras Driver + * + * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk> + * Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com> + * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> + * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 <acpi/acpi.h> +#include <acpi/actypes.h> +#include <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> +#include <linux/platform_device.h> + +#define GUID "C364AC71-36DB-495A-8494-B439D472A505" + +#define TC1100_INSTANCE_WIRELESS 1 +#define TC1100_INSTANCE_JOGDIAL 2 + +#define TC1100_LOGPREFIX "tc1100-wmi: " +#define TC1100_INFO KERN_INFO TC1100_LOGPREFIX + +MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho"); +MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505"); + +static int tc1100_probe(struct platform_device *device); +static int tc1100_remove(struct platform_device *device); +static int tc1100_suspend(struct platform_device *device, pm_message_t state); +static int tc1100_resume(struct platform_device *device); + +static struct platform_driver tc1100_driver = { + .driver = { + .name = "tc1100-wmi", + .owner = THIS_MODULE, + }, + .probe = tc1100_probe, + .remove = tc1100_remove, + .suspend = tc1100_suspend, + .resume = tc1100_resume, +}; + +static struct platform_device *tc1100_device; + +struct tc1100_data { + u32 wireless; + u32 jogdial; +}; + +static struct tc1100_data suspend_data; + +/* -------------------------------------------------------------------------- + Device Management + -------------------------------------------------------------------------- */ + +static int get_state(u32 *out, u8 instance) +{ + u32 tmp; + acpi_status status; + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + + if (!out) + return -EINVAL; + + if (instance > 2) + return -ENODEV; + + status = wmi_query_block(GUID, instance, &result); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obj = (union acpi_object *) result.pointer; + if (obj && obj->type == ACPI_TYPE_BUFFER && + obj->buffer.length == sizeof(u32)) { + tmp = *((u32 *) obj->buffer.pointer); + } else { + tmp = 0; + } + + if (result.length > 0 && result.pointer) + kfree(result.pointer); + + switch (instance) { + case TC1100_INSTANCE_WIRELESS: + *out = (tmp == 3) ? 1 : 0; + return 0; + case TC1100_INSTANCE_JOGDIAL: + *out = (tmp == 1) ? 1 : 0; + return 0; + default: + return -ENODEV; + } +} + +static int set_state(u32 *in, u8 instance) +{ + u32 value; + acpi_status status; + struct acpi_buffer input; + + if (!in) + return -EINVAL; + + if (instance > 2) + return -ENODEV; + + switch (instance) { + case TC1100_INSTANCE_WIRELESS: + value = (*in) ? 1 : 2; + break; + case TC1100_INSTANCE_JOGDIAL: + value = (*in) ? 0 : 1; + break; + default: + return -ENODEV; + } + + input.length = sizeof(u32); + input.pointer = &value; + + status = wmi_set_block(GUID, instance, &input); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; +} + +/* -------------------------------------------------------------------------- + FS Interface (/sys) + -------------------------------------------------------------------------- */ + +/* + * Read/ write bool sysfs macro + */ +#define show_set_bool(value, instance) \ +static ssize_t \ +show_bool_##value(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + u32 result; \ + acpi_status status = get_state(&result, instance); \ + if (ACPI_SUCCESS(status)) \ + return sprintf(buf, "%d\n", result); \ + return sprintf(buf, "Read error\n"); \ +} \ +\ +static ssize_t \ +set_bool_##value(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + u32 tmp = simple_strtoul(buf, NULL, 10); \ + acpi_status status = set_state(&tmp, instance); \ + if (ACPI_FAILURE(status)) \ + return -EINVAL; \ + return count; \ +} \ +static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \ + show_bool_##value, set_bool_##value); + +show_set_bool(wireless, TC1100_INSTANCE_WIRELESS); +show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL); + +static void remove_fs(void) +{ + device_remove_file(&tc1100_device->dev, &dev_attr_wireless); + device_remove_file(&tc1100_device->dev, &dev_attr_jogdial); +} + +static int add_fs(void) +{ + int ret; + + ret = device_create_file(&tc1100_device->dev, &dev_attr_wireless); + if (ret) + goto add_sysfs_error; + + ret = device_create_file(&tc1100_device->dev, &dev_attr_jogdial); + if (ret) + goto add_sysfs_error; + + return ret; + +add_sysfs_error: + remove_fs(); + return ret; +} + +/* -------------------------------------------------------------------------- + Driver Model + -------------------------------------------------------------------------- */ + +static int tc1100_probe(struct platform_device *device) +{ + int result = 0; + + result = add_fs(); + return result; +} + + +static int tc1100_remove(struct platform_device *device) +{ + remove_fs(); + return 0; +} + +static int tc1100_suspend(struct platform_device *dev, pm_message_t state) +{ + int ret; + + ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS); + if (ret) + return ret; + + ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL); + if (ret) + return ret; + + return ret; +} + +static int tc1100_resume(struct platform_device *dev) +{ + int ret; + + ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS); + if (ret) + return ret; + + ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL); + if (ret) + return ret; + + return ret; +} + +static int __init tc1100_init(void) +{ + int result = 0; + + if (!wmi_has_guid(GUID)) + return -ENODEV; + + result = platform_driver_register(&tc1100_driver); + if (result) + return result; + + tc1100_device = platform_device_alloc("tc1100-wmi", -1); + platform_device_add(tc1100_device); + + printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras loaded\n"); + + return result; +} + +static void __exit tc1100_exit(void) +{ + platform_device_del(tc1100_device); + platform_driver_unregister(&tc1100_driver); + + printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras unloaded\n"); +} + +module_init(tc1100_init); +module_exit(tc1100_exit); |