* [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
@ 2008-02-05 2:17 ` Carlos Corbacho
2008-02-05 2:17 ` [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
` (4 subsequent siblings)
5 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:17 UTC (permalink / raw)
To: linux-acpi
Cc: Carlos Corbacho, Len Brown, Matthew Garrett, Alexey Starikovskiy
The following is an implementation of the Windows Management
Instrumentation (WMI) ACPI interface mapper (PNP0C14).
What it does:
Parses the _WDG method and exports functions to process WMI method calls,
data block query/ set commands (both based on GUID) and does basic event
handling.
How: WMI presents an in kernel interface here (essentially, a minimal
wrapper around ACPI)
(const char *guid assume the 36 character ASCII representation of
a GUID - e.g. 67C3371D-95A3-4C37-BB61-DD47B491DAAB)
wmi_evaluate_method(const char *guid, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out)
wmi_query_block(const char *guid, u8 instance,
struct acpi_buffer *out)
wmi_set_block(const char *guid, u38 instance,
const struct acpi_buffer *in)
wmi_install_notify_handler(acpi_notify_handler handler);
wmi_remove_notify_handler(void);
wmi_get_event_data(u32 event, struct acpi_buffer *out)
wmi_has_guid(const char guid*)
wmi_has_guid() is a helper function to find if a GUID exists or not on the
system (a quick and easy way for WMI dependant drivers to see if the
the method/ block they want exists, since GUIDs are supposed to be unique).
Event handling - allow a WMI based driver to register a notifier handler
for each GUID with WMI. When a notification is sent to a GUID in WMI, the
handler registered with WMI is then called (it is left to the caller to
ask for the WMI event data associated with the GUID, if needed).
What it won't do:
Unicode - The MS article[1] calls for converting between ASCII and Unicode (or
vice versa) if a GUID is marked as "string". This is left up to the calling
driver.
Handle a MOF[1] - the WMI mapper just exports methods, data and events to
userspace. MOF handling is down to userspace.
Userspace interface - this will be added later.
[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
===
ChangeLog
==
v1 (2007-10-02):
* Initial release
v2 (2007-10-05):
* Cleaned up code - split up super "wmi_evaluate_block" -> each external
symbol now handles its own ACPI calls, rather than handing off to
a "super" method (and in turn, is a lot simpler to read)
* Added a find_guid() symbol - return true if a given GUID exists on
the system
* wmi_* functions now return type acpi_status (since they are just
fancy wrappers around acpi_evaluate_object())
* Removed extra debug code
v3 (2007-10-27)
* More code clean up - now passes checkpatch.pl
* Change data block calls - ref MS spec, method ID is not required for
them, so drop it from the function parameters.
* Const'ify guid in the function call parameters.
* Fix _WDG buffer handling - copy the data to our own private structure.
* Change WMI from tristate to bool - otherwise the external functions are
not exported in linux/acpi.h if you try to build WMI as a module.
* Fix more flag comparisons.
* Add a maintainers entry - since I wrote this, I should take the blame
for it.
v4 (2007-10-30)
* Add missing brace from after fixing checkpatch errors.
* Rewrote event handling - allow external drivers to register with WMI to
handle WMI events
* Clean up flags and sanitise flag handling
v5 (2007-11-03)
* Add sysfs interface for userspace. Export events over netlink again.
* Remove module left overs, fully convert to built-in driver.
* Tweak in-kernel API to use u8 for instance, since this is what the GUID
blocks use (so instance cannot be greater than u8).
* Export wmi_get_event_data() for in kernel WMI drivers.
v6 (2007-11-07)
* Split out userspace into a different patch
v7 (2007-11-20)
* Fix driver to handle multiple PNP0C14 devices - store all GUIDs using
the kernel's built in list functions, and just keep adding to the list
every time we handle a PNP0C14 devices - GUIDs will always be unique,
and WMI callers do not know or care about different devices.
* Change WMI event handler registration to use its' own event handling
struct; we should not pass an acpi_handle down to any WMI based drivers
- they should be able to function with only the calls provided in WMI.
* Update my e-mail address
v8 (2007-11-28)
* Convert back to a module.
* Update Kconfig to default to building as a module.
* Remove an erroneous printk.
* Simply comments for string flag (since we now leave the handling to the
caller).
v9 (2007-12-07)
* Add back missing MODULE_DEVICE_TABLE for autoloading
* Checkpatch fixes
v10 (2007-12-12)
* Workaround broken GUIDs declared expensive without a WCxx method.
* Minor cleanups
v11 (2007-12-17)
* More fixing for broken GUIDs declared expensive without a WCxx method.
* Add basic EmbeddedControl region handling.
v12 (2007-12-18)
* Changed EC region handling code, as per Alexey's comments.
v13 (2007-12-27)
* Changed event handling so that we can have one event handler registered
per GUID, as per Matthew Garrett's suggestion.
v14 (2008-01-12)
* Remove ACPI debug statements
v15 (2008-02-01)
* Replace two remaining 'x == NULL' type tests with '!x'
v16 (2008-02-05)
* Change MAINTAINERS entry, as I am not, and never have been, paid to work
on WMI
* Remove 'default' line from Kconfig
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Alexey Starikovskiy <aystarik@gmail.com>
---
MAINTAINERS | 7
drivers/acpi/Kconfig | 10 +
drivers/acpi/Makefile | 1
drivers/acpi/wmi.c | 710 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/acpi.h | 20 +
5 files changed, 748 insertions(+), 0 deletions(-)
create mode 100644 drivers/acpi/wmi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 58b1603..2421b20 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -259,6 +259,13 @@ L: linux-acpi@vger.kernel.org
W: http://acpi.sourceforge.net/
S: Supported
+ACPI WMI DRIVER
+P: Carlos Corbacho
+M: carlos@strangeworlds.co.uk
+L: linux-acpi@vger.kernel.org
+W: http://www.lesswatts.org/projects/acpi/
+S: Maintained
+
ADM1025 HARDWARE MONITOR DRIVER
P: Jean Delvare
M: khali@linux-fr.org
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 5583729..0065f37 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 456446f..f29812a 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 0000000..36b84ab
--- /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/include/linux/acpi.h b/include/linux/acpi.h
index de09704..9f2ee42 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -191,6 +191,26 @@ extern int ec_transaction(u8 command,
#endif /*CONFIG_ACPI_EC*/
+#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
+
+typedef void (*wmi_notify_handler) (u32 value, void *context);
+
+extern acpi_status wmi_evaluate_method(const char *guid, u8 instance,
+ u32 method_id,
+ const struct acpi_buffer *in,
+ struct acpi_buffer *out);
+extern acpi_status wmi_query_block(const char *guid, u8 instance,
+ struct acpi_buffer *out);
+extern acpi_status wmi_set_block(const char *guid, u8 instance,
+ const struct acpi_buffer *in);
+extern acpi_status wmi_install_notify_handler(const char *guid,
+ wmi_notify_handler handler, void *data);
+extern acpi_status wmi_remove_notify_handler(const char *guid);
+extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
+extern bool wmi_has_guid(const char *guid);
+
+#endif /* CONFIG_ACPI_WMI */
+
extern int acpi_blacklisted(void);
#ifdef CONFIG_DMI
extern void acpi_dmi_osi_linux(int enable, const struct dmi_system_id *d);
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
2008-02-05 2:17 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
@ 2008-02-05 2:17 ` Carlos Corbacho
2008-02-05 2:17 ` [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
` (3 subsequent siblings)
5 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:17 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
v4:
* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
WMID laptops.
v5:
* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
to ensure that the correct value is always set.
v6:
* Fix 'use before initialisation' bug with quirks.
v7
* Fix bug on AMW0 where acer-wmi would exit if a mail LED was not
detected.
* Add Acer Aspire 9110 mail LED support
* Fix section mismatch warnings
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
MAINTAINERS | 7
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 1109 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1133 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2421b20..12bda1b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -209,6 +209,13 @@ L: linux-scsi@vger.kernel.org
W: http://www.adaptec.com/
S: Supported
+ACER WMI LAPTOP EXTRAS
+P: Carlos Corbacho
+M: carlos@strangeworlds.co.uk
+L: aceracpi@googlegroups.com (subscribers-only)
+W: http://code.google.com/p/aceracpi
+S: Maintained
+
ACPI
P: Len Brown
M: len.brown@intel.com
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f20c30c..9f20faa 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index a9e8faf..7adf02f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..a4d6775
--- /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);
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
2008-02-05 2:17 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
2008-02-05 2:17 ` [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
@ 2008-02-05 2:17 ` Carlos Corbacho
2008-02-05 2:17 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
` (2 subsequent siblings)
5 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:17 UTC (permalink / raw)
To: linux-acpi
Cc: Carlos Corbacho, Len Brown, Matthew Garrett, Jamey Hicks,
Joshua Wise
This is based on the 2004 out-of-tree work of Jamey Hicks, to add
support via WMI for controlling the jog dial and wireless on these
tablets.
v1:
Original release
v2:
As per Joshua Wise's comments, change bluetooth to jogdial (an error from
the original driver).
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Jamey Hicks <jamey.hicks@nokia.com>
CC: Joshua Wise <joshua@joshuawise.com>
---
MAINTAINERS | 5 +
drivers/misc/Kconfig | 9 +
drivers/misc/Makefile | 1
drivers/misc/tc1100-wmi.c | 290 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 305 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/tc1100-wmi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 12bda1b..2685c06 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1803,6 +1803,11 @@ P: Jaroslav Kysela
M: perex@perex.cz
S: Maintained
+HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER
+P: Carlos Corbacho
+M: carlos@strangeworlds.co.uk
+S: Odd Fixes
+
HPET: High Precision Event Timers driver (hpet.c)
P: Clemens Ladisch
M: clemens@ladisch.de
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 9f20faa..78cd338 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -142,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 7adf02f..1f41654 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -9,6 +9,7 @@ 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/tc1100-wmi.c b/drivers/misc/tc1100-wmi.c
new file mode 100644
index 0000000..f25e4c9
--- /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);
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
` (2 preceding siblings ...)
2008-02-05 2:17 ` [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
@ 2008-02-05 2:17 ` Carlos Corbacho
2008-02-05 22:59 ` Carlos Corbacho
2008-08-27 13:53 ` Matthew Garrett
2008-02-05 2:17 ` [PATCH 5/5] ACPI: WMI: Add documentation Carlos Corbacho
2008-02-05 21:03 ` [PATCH 0/5] WMI patches for acpi-test (v2) Len Brown
5 siblings, 2 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:17 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.
Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).
For now, the length of the GUID name/ directory is limited to 19
characters, due to the current hardcoded limit on bus_id. When this is
fixed, this can be changed in the code.
v1 (2007-11-03)
* Initial release
v2 (2007-11-07)
* Split out into a separate patch
v3 (2007-11-20)
* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
should all be mutually exclusive - we do not want the input data to
change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
store it, but don't execute the method until 'data' is read. This is
due to the fact that it is perfectly acceptable to have a WMI method
that does not take any input (and it is easier to trigger an execute
on reading the file, and not return anything if there is nothing to
return, rather than trying to write values that aren't required, or
may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
handle ASCII (as per ACPI), and leave it up to userspace to convert
to/ from whatever encoding they wish to use.
v4 (2007-11-28)
* Add code to remove sysfs files on module removal
v5 (2007-12-08)
* Convert to a class device, instead of trying to manually create and
manipulate kobject's - this allows us to add module autoloading for WMI
based drivers, and will (hopefully) be less likely to break with the
latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
instance and instance_count, and use these to provide the instance
to call for a given GUID (while still communicating the maximum number
of instances available - for now, we will always respect instance_count
when setting instance - so any instance > instance_count will be
discarded).
v6 (2007-12-12)
* Add string file - this reports back if the GUID has the string flag set.
v8 (2008-01-18)
* Add new in kernel function, wmi_get_device(), to take advantage of the
class and virtual devices, and return the virtual device for a GUID.
v9 (2008-02-04)
* Fold in bus_id length workaround
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/acpi/Kconfig | 10 +
drivers/acpi/wmi.c | 547 ++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/acpi.h | 1
3 files changed, 556 insertions(+), 2 deletions(-)
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 0065f37..646b6f7 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -210,6 +210,16 @@ config ACPI_WMI
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_WMI_SYSFS_DEVEL
+ bool "WMI future sysfs interface"
+ default n
+ depends on ACPI_WMI
+ depends on SYSFS
+ help
+ This creates a sysfs interface for the GUIDs associated with WMI.
+ This is currently in development, so should only be enabled by testers
+ or developers.
+
config ACPI_ASUS
tristate "ASUS/Medion Laptop Extras"
depends on X86
diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 36b84ab..499add8 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
@@ -66,6 +68,11 @@ struct wmi_block {
acpi_handle handle;
wmi_notify_handler handler;
void *handler_data;
+ struct device *dev;
+ u8 instance;
+ u32 method_id;
+ void *pointer;
+ acpi_size length;
};
static struct wmi_block wmi_blocks;
@@ -194,6 +201,35 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest)
return true;
}
+#ifdef CONFIG_ACPI_WMI_SYSFS_DEVEL
+/*
+ * 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);
+
+ return 0;
+}
+#endif
+
static bool find_guid(const char *guid_string, struct wmi_block **out)
{
char tmp[16], guid_input[16];
@@ -517,6 +553,503 @@ bool wmi_has_guid(const char *guid_string)
EXPORT_SYMBOL_GPL(wmi_has_guid);
/*
+ * Until sysfs support is stabilised, allow it to be disabled
+ */
+#ifdef CONFIG_ACPI_WMI_SYSFS_DEVEL
+/**
+ * wmi_get_device - Get the virtual device associated with a GUID
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dev: Pointer to pointer to the device
+ *
+ * Return a pointer to a pointer to virtual device associated with a GUID
+ */
+int wmi_get_device(const char *guid_string, struct device **dev)
+{
+ struct wmi_block *block;
+ bool ret;
+
+ ret = find_guid(guid_string, &block);
+ if (!ret)
+ return -ENODEV;
+
+ *dev = block->dev;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wmi_get_device);
+
+/*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+ *bin_attr, char *buf, loff_t offset, size_t count) {
+ u8 instance;
+ u32 method_id;
+ const char *guid;
+ struct acpi_buffer in;
+ struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+ acpi_status status;
+ union acpi_object *obj;
+ struct wmi_block *block;
+ struct device *dev;
+ char guid_string[37];
+
+ dev = container_of(kobj, struct device, kobj);
+ if (!dev)
+ return -ENODEV;
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ guid = block->gblock.guid;
+ if (!guid)
+ return -EINVAL;
+
+ instance = block->instance;
+ if (instance == 0)
+ return -EINVAL;
+
+ wmi_gtoa(block->gblock.guid, guid_string);
+
+ if (block->gblock.flags & ACPI_WMI_METHOD) {
+ method_id = block->method_id;
+ if (method_id == 0)
+ return -EINVAL;
+
+ mutex_lock(&wmi_data_lock);
+ if (block->pointer) {
+ in.pointer = block->pointer;
+ in.length = block->length;
+ status = wmi_evaluate_method(guid_string, instance,
+ method_id, &in, &out);
+ } else {
+ status = wmi_evaluate_method(guid_string, instance,
+ method_id, NULL, &out);
+ }
+ kfree(block->pointer);
+ block->length = 0;
+ mutex_unlock(&wmi_data_lock);
+ } else {
+ status = wmi_query_block(guid_string, instance, &out);
+ }
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = (union acpi_object *) out.pointer;
+
+ if (!obj || obj->buffer.length == 0)
+ return -EIO;
+
+ switch (obj->type) {
+ case (ACPI_TYPE_STRING):
+ case (ACPI_TYPE_BUFFER):
+ block->pointer = kzalloc(obj->buffer.length, GFP_KERNEL);
+ if (!block->pointer)
+ return -ENOMEM;
+
+ memcpy(block->pointer, obj->buffer.pointer, obj->buffer.length);
+ kfree(out.pointer);
+ buf = block->pointer;
+ break;
+ default:
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+ *bin_attr, char *buf, loff_t offset, size_t count){
+ struct wmi_block *block;
+ struct acpi_buffer in;
+ acpi_status status;
+ struct device *dev;
+ char guid_string[37];
+
+ dev = container_of(kobj, struct device, kobj);
+ if (!dev)
+ return -ENODEV;
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ if (block->instance == 0)
+ return -EINVAL;
+
+ if (count == 0)
+ return count;
+
+ if (block->gblock.flags & ACPI_WMI_METHOD) {
+ kfree(block->pointer);
+ block->pointer = kzalloc(count, GFP_KERNEL);
+ if (!block->pointer)
+ return count;
+
+ memcpy(block->pointer, buf, count);
+ block->length = count;
+ } else {
+ in.pointer = buf;
+ in.length = count;
+ wmi_gtoa(block->gblock.guid, guid_string);
+ status = wmi_set_block(guid_string, block->instance, &in);
+ }
+
+ return count;
+}
+
+static struct bin_attribute wmi_attr_data = {
+ .attr = {
+ .name = "data",
+ .mode = 0600
+ },
+ .size = 0,
+ .read = wmi_data_read,
+ .write = wmi_data_write,
+};
+
+static ssize_t wmi_event_data_read(struct kobject *kobj, struct bin_attribute
+ *bin_attr, char *buf, loff_t offset, size_t count) {
+ struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obj;
+ struct wmi_block *block;
+ struct device *dev;
+
+ dev = container_of(kobj, struct device, kobj);
+ if (!dev)
+ return -ENODEV;
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ wmi_get_event_data(block->gblock.notify_id, &out);
+
+ obj = (union acpi_object *) out.pointer;
+ buf = obj->buffer.pointer;
+
+ return 0;
+}
+
+static struct bin_attribute wmi_attr_event_data = {
+ .attr = {
+ .name = "data",
+ .mode = 0400
+ },
+ .size = 0,
+ .read = wmi_event_data_read,
+};
+
+static ssize_t show_type(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct guid_block *block;
+ struct wmi_block *wblock;
+
+ wblock = dev->driver_data;
+ if (!wblock)
+ return sprintf(buf, "Error\n");
+
+ block = &wblock->gblock;
+
+ if (block->flags & ACPI_WMI_METHOD) {
+ return sprintf(buf, "method\n");
+ } else if (block->flags & ACPI_WMI_EVENT) {
+ return sprintf(buf, "event\n");
+ } else {
+ return sprintf(buf, "data\n");
+ }
+}
+static DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wmi_block *block;
+
+ block = dev->driver_data;
+ if (!block)
+ return sprintf(buf, "Error\n");
+
+ return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wmi_block *block;
+
+ block = dev->driver_data;
+ if (!block)
+ return sprintf(buf, "Error\n");
+
+ return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct wmi_block *block;
+ u8 instance;
+
+ instance = simple_strtoul(buf, NULL, 10);
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ if (instance <= block->gblock.instance_count) {
+ block->instance = instance;
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wmi_block *block;
+
+ block = dev->driver_data;
+ if (!block)
+ return sprintf(buf, "Error\n");
+
+ return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct wmi_block *block;
+ u8 method_id;
+
+ method_id = simple_strtoul(buf, NULL, 10);
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ mutex_lock(&wmi_data_lock);
+ block->method_id = method_id;
+ mutex_unlock(&wmi_data_lock);
+
+ return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+ set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wmi_block *block;
+ struct guid_block *gblock;
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ gblock = &block->gblock;
+
+ return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wmi_block *block;
+ struct guid_block *gblock;
+
+ block = dev->driver_data;
+ if (!block)
+ return -EINVAL;
+
+ gblock = &block->gblock;
+
+ return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, 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->driver_data;
+ 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;
+ guid_dev->driver_data = wblock;
+
+ gblock = &wblock->gblock;
+
+ wmi_gtoa(gblock->guid, guid_string);
+ memcpy(guid_dev->bus_id, guid_string, 19);
+
+ result = device_register(guid_dev);
+ if (result)
+ return result;
+
+ result = device_create_file(guid_dev, &dev_attr_type);
+ if (result)
+ return result;
+
+ result = device_create_file(guid_dev, &dev_attr_string);
+ if (result)
+ return result;
+
+ if (gblock->flags & ACPI_WMI_EVENT) {
+ result = device_create_file(guid_dev,
+ &dev_attr_notification);
+ if (result)
+ return result;
+
+ result = device_create_bin_file(guid_dev,
+ &wmi_attr_event_data);
+ if (result)
+ return result;
+ break;
+ }
+
+ result = device_create_file(guid_dev, &dev_attr_instance_count);
+ if (result)
+ return result;
+
+ result = device_create_file(guid_dev, &dev_attr_instance);
+ if (result)
+ return result;
+
+ result = device_create_bin_file(guid_dev, &wmi_attr_data);
+ if (result)
+ return result;
+
+ if (gblock->flags & ACPI_WMI_METHOD) {
+ result = device_create_file(guid_dev,
+ &dev_attr_method_id);
+ 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_type);
+ device_remove_file(guid_dev, &dev_attr_string);
+ device_remove_file(guid_dev, &dev_attr_notification);
+ device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+ device_remove_file(guid_dev, &dev_attr_instance_count);
+ device_remove_file(guid_dev, &dev_attr_instance);
+ device_remove_bin_file(guid_dev, &wmi_attr_data);
+ device_remove_file(guid_dev, &dev_attr_method_id);
+
+ kfree(wblock->pointer);
+ 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;
+}
+#else
+int wmi_get_device(const char *guid_string, struct device **dev)
+{
+ return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(wmi_get_device);
+
+static int wmi_class_init(void)
+{
+ return 0;
+}
+
+static void wmi_class_exit(void)
+{
+ return;
+}
+#endif
+
+/*
* Parse the _WDG method for the GUID data blocks
*/
static __init acpi_status parse_wdg(acpi_handle handle)
@@ -671,6 +1204,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
static int __init acpi_wmi_init(void)
{
+ int ret;
acpi_status result;
if (acpi_disabled)
@@ -682,10 +1216,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;
}
+ ret = wmi_class_init();
+ if (ret) {
+ acpi_bus_unregister_driver(&acpi_wmi_driver);
+ return ret;
+ }
+
+ printk(KERN_INFO PREFIX "Mapper loaded\n");
+
return result;
}
@@ -694,6 +1235,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) {
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 9f2ee42..8cbbeb2 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -208,6 +208,7 @@ extern acpi_status wmi_install_notify_handler(const char *guid,
extern acpi_status wmi_remove_notify_handler(const char *guid);
extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
extern bool wmi_has_guid(const char *guid);
+extern int wmi_get_device(const char *guid, struct device **dev);
#endif /* CONFIG_ACPI_WMI */
^ permalink raw reply related [flat|nested] 31+ messages in thread* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-02-05 2:17 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
@ 2008-02-05 22:59 ` Carlos Corbacho
2008-02-06 11:36 ` Carlos Corbacho
2008-08-27 13:53 ` Matthew Garrett
1 sibling, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 22:59 UTC (permalink / raw)
To: linux-acpi; +Cc: Len Brown, Matthew Garrett
On Tuesday 05 February 2008 02:17:21 Carlos Corbacho wrote:
> Create a 'wmi' class, and populate it with a virtual device for each GUID.
> This also allows us to autoload WMI drivers based on GUID
> via MODULE_ALIAS.
>
> Under each GUID are a set of files that can be read and written to from
> userspace (these will be fully documented in a later patch).
>
> For now, the length of the GUID name/ directory is limited to 19
> characters, due to the current hardcoded limit on bus_id. When this is
> fixed, this can be changed in the code.
I'm starting to have some serious doubts about using sysfs for communicating
with userspace (I'm happy with the class/ virtual device ID idea, and I don't
see that being dropped, since it also solves the WMI autoloading problem
quite neatly - but the extra sysfs files added on to allow userspace to
interact with WMI are where I have a problem).
The problem is that a lot of the interactions with WMI are transactional - we
pass something in, and we expect to get something out, and we don't want
anything to happen in the middle to that data.
At the moment, this is the snippet of (Python) code I have to execute a WMI
method from userspace (instance and method_id in this case are just integers,
data_in and data_out can be any data structure, and are whatever the WMI
method accepts & returns, respectively):
def evaluate_method(guid, instance, method_id, data_in, data_out):
# Check that the instance is not bigger than the allowed maximum
# The maximum is defined by the GUID entry in the DSDT
instance_count_file = open(path + guid + "/instance_count", "r")
if int(instance) > int(instance_count_file.read()):
print "Instance is outside allowed maximum range"
exit()
instance_count_file.close()
# Specify the instance we want to execute
instance_file = open(path + guid + "/instance", "w")
instance_file.write(instance)
# Specify the method we want to execute
method_id_file = open(path + guid + "/method_id", "w")
method_id_file.write(method_id)
# Write the input data in
data_file = open(path + guid + "/data", "wb")
data_file.write(data_in)
data_file.close()
# Read the data back to trigger execution
data_file = open(path + guid + "/data", "rb")
data_out = data_file.read()
data_file.close()
method_id_file.close()
instance_file.close()
Unfortunately, as you can see this isn't nice, and very racy. To point out the
more glaring flaws:
1) instance and method_id can be changed before we start reading and writing
in any data.
2) Executing a method involves reading back from the 'data' file (but if we
don't care about the output, this makes little sense). If anything changes
before this, then either the method will fail, or we may not execute what we
intended to.
3) For method execution, this really comes across as rather 'clunky' means to
do so
At this point, I'm seriously looking into replacing the sysfs userspace
interface with ioctl's instead, but I'd like some thoughts either way, as I'm
not that sure where to go from here.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-02-05 22:59 ` Carlos Corbacho
@ 2008-02-06 11:36 ` Carlos Corbacho
2008-02-07 3:49 ` Len Brown
0 siblings, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-06 11:36 UTC (permalink / raw)
To: linux-acpi; +Cc: Len Brown, Matthew Garrett
On Tuesday 05 February 2008 22:59:24 Carlos Corbacho wrote:
> Unfortunately, as you can see this isn't nice, and very racy. To point out
> the more glaring flaws:
>
> 1) instance and method_id can be changed before we start reading and
> writing in any data.
>
> 2) Executing a method involves reading back from the 'data' file (but if we
> don't care about the output, this makes little sense). If anything changes
> before this, then either the method will fail, or we may not execute what
> we intended to.
On these two points, we could use locking (but this would only be advisory,
and rely on other programs not trying to write while we do), but we'd need to
rejig how this works a bit (since reading 'data' here triggers execution.
(So, basically, in future, shoving this all into a C library to abstract the
WMI sysfs file access).
> 3) For method execution, this really comes across as rather 'clunky' means
> to do so
This point is still valid.
Maybe changing a method to the following:
<GUID>/
|--> instance_count
|--> instance
|--> method_id
|--> in
|--> out
|--> execute
Then to execute a method:
1) Write lock instance, method_id, in, out and execute.
2) Write the instance and method_id to be executed to their respective files.
Write the input data for the method to 'in'.
3) Write '1' to execute the method, store the returned data in 'out'
(writing '0' to 'execute' would clear everything instead). (We can always
return the error code by reading execute).
4) Read 'out' to retrieve any data.
(I don't want to make reading 'out' trigger the execution, since we can't
guarantee a read lock between languages).
For data blocks, we can do something similar.
Any thoughts? It's still a little clunky, and I'm still not sure whether an
ioctl would still be preferable to all this?
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-02-06 11:36 ` Carlos Corbacho
@ 2008-02-07 3:49 ` Len Brown
0 siblings, 0 replies; 31+ messages in thread
From: Len Brown @ 2008-02-07 3:49 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Matthew Garrett
On Wednesday 06 February 2008 06:36, Carlos Corbacho wrote:
> On Tuesday 05 February 2008 22:59:24 Carlos Corbacho wrote:
> > Unfortunately, as you can see this isn't nice, and very racy. To point out
> > the more glaring flaws:
> >
> > 1) instance and method_id can be changed before we start reading and
> > writing in any data.
> >
> > 2) Executing a method involves reading back from the 'data' file (but if we
> > don't care about the output, this makes little sense). If anything changes
> > before this, then either the method will fail, or we may not execute what
> > we intended to.
>
> On these two points, we could use locking (but this would only be advisory,
> and rely on other programs not trying to write while we do), but we'd need to
> rejig how this works a bit (since reading 'data' here triggers execution.
>
> (So, basically, in future, shoving this all into a C library to abstract the
> WMI sysfs file access).
In either case, ioctl or sysfs, a library is probably appropriate.
However, with the ioctl, it sounds like the driver is able to enforce
consistency of a transaction, while multiple users of a sysfs I/F
may get confused, or worse.
So the question is if the sysfs I/F is useful for development or not,
since it doesn't look like the right I/F in the long run.
Shall I drop patches #4 and #5, or would you like to send an update?
thanks,
-Len
> > 3) For method execution, this really comes across as rather 'clunky' means
> > to do so
>
> This point is still valid.
>
> Maybe changing a method to the following:
>
> <GUID>/
> |--> instance_count
> |--> instance
> |--> method_id
> |--> in
> |--> out
> |--> execute
>
> Then to execute a method:
>
> 1) Write lock instance, method_id, in, out and execute.
>
> 2) Write the instance and method_id to be executed to their respective files.
> Write the input data for the method to 'in'.
>
> 3) Write '1' to execute the method, store the returned data in 'out'
> (writing '0' to 'execute' would clear everything instead). (We can always
> return the error code by reading execute).
>
> 4) Read 'out' to retrieve any data.
>
> (I don't want to make reading 'out' trigger the execution, since we can't
> guarantee a read lock between languages).
>
> For data blocks, we can do something similar.
>
> Any thoughts? It's still a little clunky, and I'm still not sure whether an
> ioctl would still be preferable to all this?
>
> -Carlos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-02-05 2:17 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2008-02-05 22:59 ` Carlos Corbacho
@ 2008-08-27 13:53 ` Matthew Garrett
2008-08-27 20:21 ` Carlos Corbacho
1 sibling, 1 reply; 31+ messages in thread
From: Matthew Garrett @ 2008-08-27 13:53 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
Hi Carlos,
Now that we're getting a proliferation of WMI drivers (I'm working on a
Dell one at the moment), it'd be nice to be able to get the module
autoloading support in. BUS_ID still seems to be limited to 20 bytes,
though. Any thoughts on the best way to go from here?
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-08-27 13:53 ` Matthew Garrett
@ 2008-08-27 20:21 ` Carlos Corbacho
2008-08-27 20:44 ` Matthew Garrett
2009-04-29 20:38 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Matthew Garrett
0 siblings, 2 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-08-27 20:21 UTC (permalink / raw)
To: Matthew Garrett; +Cc: linux-acpi, Len Brown
On Wednesday 27 August 2008 14:53:27 Matthew Garrett wrote:
> Now that we're getting a proliferation of WMI drivers (I'm working on a
> Dell one at the moment), it'd be nice to be able to get the module
> autoloading support in. BUS_ID still seems to be limited to 20 bytes,
> though. Any thoughts on the best way to go from here?
Can we abuse uevent and cram all the GUID's into just one MODALIAS line?
Alternatively, maybe just have the directories numbered 1 to X, and have a
uevent file under each, since we don't care about trying to abuse sysfs to
let userspace talk to WMI anymore.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-08-27 20:21 ` Carlos Corbacho
@ 2008-08-27 20:44 ` Matthew Garrett
2008-08-29 15:05 ` [PATCH] Documentation: Provide Documenation/udev.txt Thomas Renninger
2009-04-29 20:38 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Matthew Garrett
1 sibling, 1 reply; 31+ messages in thread
From: Matthew Garrett @ 2008-08-27 20:44 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
On Wed, Aug 27, 2008 at 09:21:54PM +0100, Carlos Corbacho wrote:
> Can we abuse uevent and cram all the GUID's into just one MODALIAS line?
Looking at the udev rules, I suspect we need one event per GUID.
> Alternatively, maybe just have the directories numbered 1 to X, and have a
> uevent file under each, since we don't care about trying to abuse sysfs to
> let userspace talk to WMI anymore.
Yeah, that'd work for me.
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH] Documentation: Provide Documenation/udev.txt
2008-08-27 20:44 ` Matthew Garrett
@ 2008-08-29 15:05 ` Thomas Renninger
2008-08-29 15:30 ` Kay Sievers
0 siblings, 1 reply; 31+ messages in thread
From: Thomas Renninger @ 2008-08-29 15:05 UTC (permalink / raw)
To: Matthew Garrett; +Cc: Carlos Corbacho, linux-acpi, Len Brown, Kay Sievers
Subject was:
Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
On Wednesday 27 August 2008 22:44:49 Matthew Garrett wrote:
> On Wed, Aug 27, 2008 at 09:21:54PM +0100, Carlos Corbacho wrote:
> > Can we abuse uevent and cram all the GUID's into just one MODALIAS line?
>
> Looking at the udev rules, I suspect we need one event per GUID.
>
> > Alternatively, maybe just have the directories numbered 1 to X, and have
> > a uevent file under each, since we don't care about trying to abuse sysfs
> > to let userspace talk to WMI anymore.
>
> Yeah, that'd work for me.
You should add Kay if you get stuck...
Based on Kay's recent description on the linux-acpi list I set up
this documentation. IMO it's worth adding, Andi, Kay?
Thomas
-----
From: Kay Sievers <kay.sievers@vrfy.org>
Documentation: Add udev.txt based on Kay's short summary
Signed-off-by: Thomas Renninger <trenn@suse.de>
diff --git a/Documentation/00-INDEX b/Documentation/00-INDEX
index 5b5aba4..ce9dd7a 100644
--- a/Documentation/00-INDEX
+++ b/Documentation/00-INDEX
@@ -361,6 +361,8 @@ time_interpolators.txt
- info on time interpolators.
tty.txt
- guide to the locking policies of the tty layer.
+udev.txt
+ - How autoloading of modules work.
uml/
- directory with information about User Mode Linux.
unicode.txt
diff --git a/Documentation/udev.txt b/Documentation/udev.txt
new file mode 100644
index 0000000..ceb0e40
--- /dev/null
+++ b/Documentation/udev.txt
@@ -0,0 +1,37 @@
+Maintainer of udev: Kay Sievers <kay.sievers@vrfy.org>
+
+Abstract:
+This is about getting a specific driver automatically loaded
+at boot time via udev. Depending on whether the kernel detects
+that the HW, the driver is supporting, is available.
+
+
+Devices, any kind of device, can export a match, based on specific
+properties of the subsystem it belongs to. In most cases its the same
+property/id that is used inside the kernel, to find a (already loaded)
+driver which will bind this device.
+
+Any unique string, hardware ID's, whatever, are stuffed into a modalias,
+prefixed by "<subsystem>:" to be unique.
+
+PCI and USB are pretty obvious, they just stuff all their hardware ID'S
+into a string, in a defined order, and let every device export that value
+to userspace by MODALIAS environment key and the "modalias" sysfs file.
+Other subsystem may have simple strings to match, they define
+themselves.
+
+Now, the drivers contain "match id tables" which are used by the core
+to bind devices to drivers. These tables area made available to
+file2alias.c in the module postprocessing, and will end up in the
+module file itself. The string is mangled to contain wildcards, so
+they can just be fnmatch()'d against the modalias value, which the
+devices export.
+
+The string embedded in the modules are extracted by depmod, and put
+into the modules.aliases file in /lib/modules/. Every time modprobe
+is called with an alias, it searches through this file and loads all
+modules which contained a matching alias string.
+
+Udev does nothing but stupidly running modprobe for every device which
+contains MODALIAS in the event environment, and passes $MODALIAS as
+an argument to modprobe.
^ permalink raw reply related [flat|nested] 31+ messages in thread* Re: [PATCH] Documentation: Provide Documenation/udev.txt
2008-08-29 15:05 ` [PATCH] Documentation: Provide Documenation/udev.txt Thomas Renninger
@ 2008-08-29 15:30 ` Kay Sievers
2008-08-29 15:35 ` Thomas Renninger
0 siblings, 1 reply; 31+ messages in thread
From: Kay Sievers @ 2008-08-29 15:30 UTC (permalink / raw)
To: Thomas Renninger; +Cc: Matthew Garrett, Carlos Corbacho, linux-acpi, Len Brown
On Fri, 2008-08-29 at 17:05 +0200, Thomas Renninger wrote:
> Subject was:
> Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
>
> On Wednesday 27 August 2008 22:44:49 Matthew Garrett wrote:
> > On Wed, Aug 27, 2008 at 09:21:54PM +0100, Carlos Corbacho wrote:
> > > Can we abuse uevent and cram all the GUID's into just one MODALIAS line?
How many id's of what size do we have? What would be the maximum string
length?
> > Looking at the udev rules, I suspect we need one event per GUID.
> >
> > > Alternatively, maybe just have the directories numbered 1 to X, and have
> > > a uevent file under each, since we don't care about trying to abuse sysfs
> > > to let userspace talk to WMI anymore.
A "uevent" file? That file is mechanic of the driver-core and created
automatically for every device. It has well defined behaviour, like you
can trigger a uevent action, or read the event environment. Custom
subdirs with custom "uevent" files would not do anything, I guess. You
would need a "struct device" for every directory and uevent file.
What do you have in mind? Care to paste-in some example strings?
> > Yeah, that'd work for me.
>
> You should add Kay if you get stuck...
> Based on Kay's recent description on the linux-acpi list I set up
> this documentation. IMO it's worth adding, Andi, Kay?
Oh, that sounds a bit too much like "talking". I guess, it should be
rewritten, before it goes into the Documentation directory. :)
Thanks,
Kay
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH] Documentation: Provide Documenation/udev.txt
2008-08-29 15:30 ` Kay Sievers
@ 2008-08-29 15:35 ` Thomas Renninger
0 siblings, 0 replies; 31+ messages in thread
From: Thomas Renninger @ 2008-08-29 15:35 UTC (permalink / raw)
To: Kay Sievers; +Cc: Matthew Garrett, Carlos Corbacho, linux-acpi, Len Brown
On Friday 29 August 2008 17:30:57 Kay Sievers wrote:
> On Fri, 2008-08-29 at 17:05 +0200, Thomas Renninger wrote:
...
> Oh, that sounds a bit too much like "talking". I guess, it should be
> rewritten, before it goes into the Documentation directory. :)
It's better than nothing.
I won't rewrite it. I lost too much time already the last days with
descriptions.
I mean, if you read it, you know how it works?
No need to provide a formal perfect paper?
Thomas
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface
2008-08-27 20:21 ` Carlos Corbacho
2008-08-27 20:44 ` Matthew Garrett
@ 2009-04-29 20:38 ` Matthew Garrett
2009-04-29 21:09 ` Carlos Corbacho
1 sibling, 1 reply; 31+ messages in thread
From: Matthew Garrett @ 2009-04-29 20:38 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
Now that we've got the new device_name() stuff, is it possible to add
support for generating the modalias uevents in wmi? It'd be good to get
the autoloading sorted properly.
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH 5/5] ACPI: WMI: Add documentation
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
` (3 preceding siblings ...)
2008-02-05 2:17 ` [PATCH 4/5] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
@ 2008-02-05 2:17 ` Carlos Corbacho
2008-02-05 2:25 ` [PATCH 5/5] ACPI: WMI: Add initial documentation Carlos Corbacho
2008-02-05 21:03 ` [PATCH 0/5] WMI patches for acpi-test (v2) Len Brown
5 siblings, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:17 UTC (permalink / raw)
To: linux-acpi
Add documentation for WMI-ACPI, giving basic information for WMI driver
and userspace writers.
===
ChangeLog
v1 (2007-11-28)
Initial version
v2 (2007-12-08)
Update with latest changes to sysfs interface
v3 (2008-02-05)
Update
---
Documentation/acpi/wmi.txt | 131 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 131 insertions(+), 0 deletions(-)
create mode 100644 Documentation/acpi/wmi.txt
diff --git a/Documentation/acpi/wmi.txt b/Documentation/acpi/wmi.txt
new file mode 100644
index 0000000..4d243aa
--- /dev/null
+++ b/Documentation/acpi/wmi.txt
@@ -0,0 +1,131 @@
+ACPI-WMI mapping driver
+
+Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
+
+Updated: 5th February 2008
+
+1) About this guide
+
+This guide is a basic introduction on how to interact with the ACPI-WMI mapper
+driver in the kernel - it presumes you already have a basic knowledge of
+ACPI-WMI, ACPI and the hardware you are writing the driver for.
+
+2) What is ACPI-WMI
+
+At its simplest, ACPI-WMI is a proprietary extension to the ACPI specification
+from Microsoft to allow WMI (their implementation of WBEM) to access
+instrumentation data and methods in ACPI from userspace, via the ACPI _HID
+device PNP0C14.
+
+3) What is the ACPI-WMI mapper
+
+The Linux ACPI-WMI driver is the implementation of this mapper for Linux.
+
+NOTE: The mapper does not do anything itself with the ACPI-WMI devices - it
+simply makes their functionality available to other drivers and userspace.
+
+4) Implementation
+
+A general note:
+
+ACPI-WMI has no knowledge of the size of the data blocks passed to/ from
+ACPI-WMI - this is not encoded in the DSDT, and is usually only available in
+the form of a MOF file. If this is not available, you will need to analyze
+the DSDT to get this information.
+
+You will therefore need to know in advance the size of any data structures
+to handle them.
+
+Userspace:
+
+ACPI-WMI is exposed to userspace via sysfs and netlink. Data and methods are
+exposed through sysfs, events are sent via netlink.
+
+Accessing ACPI-WMI directly through sysfs is generally not recommended -
+ideally, this should be done by a userspace library, which could also deal
+with the netlink events.
+
+The sysfs interface is in /sys/devices/virtual/wmi for WMI methods and data, as
+follows:
+
+/sys/devices/virtual/wmi/
+|
+|-> <GUID>/
+ |-> type (method, data, event)
+
+Method & data blocks
+ |-> instance (instance to execute)
+ |-> instance_count (read only - maximum number of instances available)
+ |-> data (binary data file - write input data to file, read file
+ to execute method and/ or retrieve data).
+
+To read/ write a data block, instance must be set first.
+
+Method only
+ |-> method_id (write value of method id to execute)
+
+To execute a method, instance and method_id must be set first.
+
+Events - passed to userspace via netlink. However, the extra WMI data
+associated with an event is exposed through sysfs.
+
+ |-> notification (ACPI event value)
+ |-> data (binary data file - WMI data associated with the event)
+
+Kernel interface:
+
+For drivers that want to bypass userspace (either because WBEM is overkill,
+or a userspace application is not really appropriate for want you want to do),
+and talk directly to ACPI-WMI, then the mapper also exports functions
+to do this - these are essentially thin wrappers around ACPI, so if you are
+familiar with writing an ACPI driver, then writing a WMI driver is not much more
+of a stretch.
+
+The exported ACPI-WMI functions can be found in <linux/acpi.h>
+
+The most important differences between ACPI and WMI drivers are:
+
+A) WMI based drivers should be platform drivers, not ACPI drivers.
+
+B) ACPI details such as handles and/ or path names are hidden; you use GUIDs as
+ the unique identifier to call methods instead (you don't need to care either
+ about multiple PNP0C14 devices existing - the GUIDs are always unique, and
+ these are what you will work with).
+
+C) In your probe() function, you should use wmi_has_guid() to look for the
+ GUID(s) you need for your driver.
+
+For the in kernel interface, the following functions are available:
+
+wmi_evaluate_method():
+
+Evaluate a WMI method.
+
+wmi_query_block():
+
+Return the contents of a WMI data block.
+
+wmi_set_block()
+
+Set the contents of a WMI data block.
+
+wmi_install_notify_handler():
+wmi_remove_notify_handler():
+
+Install and/ or remove a handler for a WMI event - one handler per GUID.
+
+wmi_get_event_data():
+
+Return the data associated with a given WMI event.
+
+wmi_has_guid():
+
+Returns true if a given GUID is available on the system.
+
+wmi_get_device():
+
+Return a pointer to the virtual device associated with a GUID. If you need to
+set a parent device (e.g. for an input device), use this.
+
+
+[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 5/5] ACPI: WMI: Add initial documentation
2008-02-05 2:17 ` [PATCH 5/5] ACPI: WMI: Add documentation Carlos Corbacho
@ 2008-02-05 2:25 ` Carlos Corbacho
2008-02-06 3:12 ` Randy Dunlap
0 siblings, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 2:25 UTC (permalink / raw)
To: linux-acpi; +Cc: Len Brown
ACPI: WMI: Add initial documentation
Add initial documentation for WMI-ACPI, giving basic information for WMI
kernel and userspace driver writers.
===
ChangeLog
v1 (2007-11-28)
Initial version
v2 (2007-12-08)
Update with latest changes to sysfs interface
v3 (2008-02-05)
Update
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
---
Added SOB this time... (and a few trivial fixes)
This replaces: [PATCH 5/5] ACPI: WMI: Add documentation
(AKA - I shouldn't send a patch series out at 2am...)
Documentation/acpi/wmi.txt | 131 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 131 insertions(+), 0 deletions(-)
create mode 100644 Documentation/acpi/wmi.txt
diff --git a/Documentation/acpi/wmi.txt b/Documentation/acpi/wmi.txt
new file mode 100644
index 0000000..4d243aa
--- /dev/null
+++ b/Documentation/acpi/wmi.txt
@@ -0,0 +1,131 @@
+ACPI-WMI mapping driver
+
+Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
+
+Updated: 5th February 2008
+
+1) About this guide
+
+This guide is a basic introduction on how to interact with the ACPI-WMI mapper
+driver in the kernel - it presumes you already have a basic knowledge of
+ACPI-WMI, ACPI and the hardware you are writing the driver for.
+
+2) What is ACPI-WMI
+
+At its simplest, ACPI-WMI is a proprietary extension to the ACPI specification
+from Microsoft to allow WMI (their implementation of WBEM) to access
+instrumentation data and methods in ACPI from userspace, via the ACPI _HID
+device PNP0C14.
+
+3) What is the ACPI-WMI mapper
+
+The Linux ACPI-WMI driver is the implementation of this mapper for Linux.
+
+NOTE: The mapper does not do anything itself with the ACPI-WMI devices - it
+simply makes their functionality available to other drivers and userspace.
+
+4) Implementation
+
+A general note:
+
+ACPI-WMI has no knowledge of the size of the data blocks passed to/ from
+ACPI-WMI - this is not encoded in the DSDT, and is usually only available in
+the form of a MOF file. If this is not available, you will need to analyze
+the DSDT to get this information.
+
+You will therefore need to know in advance the size of any data structures
+to handle them.
+
+Userspace:
+
+ACPI-WMI is exposed to userspace via sysfs and netlink. Data and methods are
+exposed through sysfs, events are sent via netlink.
+
+Accessing ACPI-WMI directly through sysfs is generally not recommended -
+ideally, this should be done by a userspace library, which could also deal
+with the netlink events.
+
+The sysfs interface is in /sys/devices/virtual/wmi for WMI methods and data, as
+follows:
+
+/sys/devices/virtual/wmi/
+|
+|-> <GUID>/
+ |-> type (method, data, event)
+
+Method & data blocks
+ |-> instance (instance to execute)
+ |-> instance_count (read only - maximum number of instances available)
+ |-> data (binary data file - write input data to file, read file
+ to execute method and/ or retrieve data).
+
+To read/ write a data block, instance must be set first.
+
+Method only
+ |-> method_id (write value of method id to execute)
+
+To execute a method, instance and method_id must be set first.
+
+Events - passed to userspace via netlink. However, the extra WMI data
+associated with an event is exposed through sysfs.
+
+ |-> notification (ACPI event value)
+ |-> data (binary data file - WMI data associated with the event)
+
+Kernel interface:
+
+For drivers that want to bypass userspace (either because WBEM is overkill,
+or a userspace application is not really appropriate for want you want to do),
+and talk directly to ACPI-WMI, then the mapper also exports functions
+to do this - these are essentially thin wrappers around ACPI, so if you are
+familiar with writing an ACPI driver, then writing a WMI driver is not much more
+of a stretch.
+
+The exported ACPI-WMI functions can be found in <linux/acpi.h>
+
+The most important differences between ACPI and WMI drivers are:
+
+A) WMI based drivers should be platform drivers, not ACPI drivers.
+
+B) ACPI details such as handles and/ or path names are hidden; you use GUIDs as
+ the unique identifier to call methods instead (you don't need to care either
+ about multiple PNP0C14 devices existing - the GUIDs are always unique, and
+ these are what you will work with).
+
+C) In your probe() function, you should use wmi_has_guid() to look for the
+ GUID(s) you need for your driver.
+
+For the in kernel interface, the following functions are available:
+
+wmi_evaluate_method():
+
+Evaluate a WMI method.
+
+wmi_query_block():
+
+Return the contents of a WMI data block.
+
+wmi_set_block()
+
+Set the contents of a WMI data block.
+
+wmi_install_notify_handler():
+wmi_remove_notify_handler():
+
+Install and/ or remove a handler for a WMI event - one handler per GUID.
+
+wmi_get_event_data():
+
+Return the data associated with a given WMI event.
+
+wmi_has_guid():
+
+Returns true if a given GUID is available on the system.
+
+wmi_get_device():
+
+Return a pointer to the virtual device associated with a GUID. If you need to
+set a parent device (e.g. for an input device), use this.
+
+
+[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH 5/5] ACPI: WMI: Add initial documentation
2008-02-05 2:25 ` [PATCH 5/5] ACPI: WMI: Add initial documentation Carlos Corbacho
@ 2008-02-06 3:12 ` Randy Dunlap
2008-02-06 10:51 ` Carlos Corbacho
0 siblings, 1 reply; 31+ messages in thread
From: Randy Dunlap @ 2008-02-06 3:12 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
On Tue, 5 Feb 2008 02:25:58 +0000 Carlos Corbacho wrote:
> ACPI: WMI: Add initial documentation
>
> Add initial documentation for WMI-ACPI, giving basic information for WMI
> kernel and userspace driver writers.
>
> ===
> ChangeLog
>
> v1 (2007-11-28)
>
> Initial version
>
> v2 (2007-12-08)
>
> Update with latest changes to sysfs interface
>
> v3 (2008-02-05)
>
> Update
>
> Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
> CC: Len Brown <lenb@kernel.org>
> ---
> Added SOB this time... (and a few trivial fixes)
>
> This replaces: [PATCH 5/5] ACPI: WMI: Add documentation
>
> (AKA - I shouldn't send a patch series out at 2am...)
>
> Documentation/acpi/wmi.txt | 131 ++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 131 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/acpi/wmi.txt
>
>
> diff --git a/Documentation/acpi/wmi.txt b/Documentation/acpi/wmi.txt
> new file mode 100644
> index 0000000..4d243aa
> --- /dev/null
> +++ b/Documentation/acpi/wmi.txt
> @@ -0,0 +1,131 @@
> +ACPI-WMI mapping driver
> +
> +Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
> +
> +Updated: 5th February 2008
> +
> +1) About this guide
> +
> +This guide is a basic introduction on how to interact with the ACPI-WMI mapper
> +driver in the kernel - it presumes you already have a basic knowledge of
> +ACPI-WMI, ACPI and the hardware you are writing the driver for.
> +
> +2) What is ACPI-WMI
> +
Please tell what WMI stands for.
> +At its simplest, ACPI-WMI is a proprietary extension to the ACPI specification
> +from Microsoft to allow WMI (their implementation of WBEM) to access
> +instrumentation data and methods in ACPI from userspace, via the ACPI _HID
> +device PNP0C14.
> +
> +3) What is the ACPI-WMI mapper
> +
> +The Linux ACPI-WMI driver is the implementation of this mapper for Linux.
> +
> +NOTE: The mapper does not do anything itself with the ACPI-WMI devices - it
> +simply makes their functionality available to other drivers and userspace.
> +
> +4) Implementation
> +
> +A general note:
> +
> +ACPI-WMI has no knowledge of the size of the data blocks passed to/ from
to/from
> +ACPI-WMI - this is not encoded in the DSDT, and is usually only available in
> +the form of a MOF file. If this is not available, you will need to analyze
> +the DSDT to get this information.
> +
> +You will therefore need to know in advance the size of any data structures
> +to handle them.
> +
> +Userspace:
> +
> +ACPI-WMI is exposed to userspace via sysfs and netlink. Data and methods are
> +exposed through sysfs, events are sent via netlink.
> +
> +Accessing ACPI-WMI directly through sysfs is generally not recommended -
> +ideally, this should be done by a userspace library, which could also deal
> +with the netlink events.
> +
> +The sysfs interface is in /sys/devices/virtual/wmi for WMI methods and data, as
> +follows:
> +
> +/sys/devices/virtual/wmi/
> +|
> +|-> <GUID>/
> + |-> type (method, data, event)
> +
> +Method & data blocks
> + |-> instance (instance to execute)
> + |-> instance_count (read only - maximum number of instances available)
> + |-> data (binary data file - write input data to file, read file
> + to execute method and/ or retrieve data).
> +
> +To read/ write a data block, instance must be set first.
read/write
> +
> +Method only
> + |-> method_id (write value of method id to execute)
> +
> +To execute a method, instance and method_id must be set first.
> +
> +Events - passed to userspace via netlink. However, the extra WMI data
> +associated with an event is exposed through sysfs.
> +
> + |-> notification (ACPI event value)
> + |-> data (binary data file - WMI data associated with the event)
> +
> +Kernel interface:
> +
> +For drivers that want to bypass userspace (either because WBEM is overkill,
> +or a userspace application is not really appropriate for want you want to do),
for what
> +and talk directly to ACPI-WMI, then the mapper also exports functions
> +to do this - these are essentially thin wrappers around ACPI, so if you are
> +familiar with writing an ACPI driver, then writing a WMI driver is not much more
> +of a stretch.
> +
> +The exported ACPI-WMI functions can be found in <linux/acpi.h>
in <linux/acpi.h>.
> +
> +The most important differences between ACPI and WMI drivers are:
> +
> +A) WMI based drivers should be platform drivers, not ACPI drivers.
WMI-based
> +
> +B) ACPI details such as handles and/ or path names are hidden; you use GUIDs as
and/or
> + the unique identifier to call methods instead (you don't need to care either
> + about multiple PNP0C14 devices existing - the GUIDs are always unique, and
> + these are what you will work with).
> +
> +C) In your probe() function, you should use wmi_has_guid() to look for the
> + GUID(s) you need for your driver.
> +
> +For the in kernel interface, the following functions are available:
in-kernel
> +
> +wmi_evaluate_method():
> +
> +Evaluate a WMI method.
> +
> +wmi_query_block():
> +
> +Return the contents of a WMI data block.
> +
> +wmi_set_block()
wmi_set_block():
> +
> +Set the contents of a WMI data block.
> +
> +wmi_install_notify_handler():
> +wmi_remove_notify_handler():
> +
> +Install and/ or remove a handler for a WMI event - one handler per GUID.
and/or
> +
> +wmi_get_event_data():
> +
> +Return the data associated with a given WMI event.
> +
> +wmi_has_guid():
> +
> +Returns true if a given GUID is available on the system.
> +
> +wmi_get_device():
> +
> +Return a pointer to the virtual device associated with a GUID. If you need to
Returns
> +set a parent device (e.g. for an input device), use this.
> +
> +
> +[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
> -
Are there netlink-aware apps that already know how to use this
interface? If so, where?
Thanks.
---
~Randy
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH 5/5] ACPI: WMI: Add initial documentation
2008-02-06 3:12 ` Randy Dunlap
@ 2008-02-06 10:51 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-06 10:51 UTC (permalink / raw)
To: Randy Dunlap; +Cc: linux-acpi, Len Brown
On Wednesday 06 February 2008 03:12:26 Randy Dunlap wrote:
> Please tell what WMI stands for.
Will do.
> > +ACPI-WMI has no knowledge of the size of the data blocks passed to/ from
>
> to/from
I was always taught that the correct way was to leave a space.
However, I will fix this.
> Are there netlink-aware apps that already know how to use this
> interface? If so, where?
There is currently no userspace application that can handle this (netlink is
also only used for event passing, as stated).
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 0/5] WMI patches for acpi-test (v2)
2008-02-05 2:16 [PATCH 0/5] WMI patches for acpi-test (v2) Carlos Corbacho
` (4 preceding siblings ...)
2008-02-05 2:17 ` [PATCH 5/5] ACPI: WMI: Add documentation Carlos Corbacho
@ 2008-02-05 21:03 ` Len Brown
2008-02-05 21:18 ` Carlos Corbacho
5 siblings, 1 reply; 31+ messages in thread
From: Len Brown @ 2008-02-05 21:03 UTC (permalink / raw)
To: Carlos Corbacho
Cc: linux-acpi, Matthew Garrett, Alexey Starikovskiy, linux-ia64
On Monday 04 February 2008 21:16, Carlos Corbacho wrote:
> 2) Do you know of any IA64 systems using ACPI-WMI? At the moment, WMI will work
> on x86 and hopefully IA64, but if it turns out that no vendor has or ever will
> implement ACPI-WMI on IA64, then we could safely drop it there.
I recommend skipping WMI on IA64 unless/until a real need to support it emerges.
There are no WMI extensions on the Intel Tiger or Hitachi Fusion IA64 boxes I have access to.
thanks,
-Len
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH 0/5] WMI patches for acpi-test (v2)
2008-02-05 21:03 ` [PATCH 0/5] WMI patches for acpi-test (v2) Len Brown
@ 2008-02-05 21:18 ` Carlos Corbacho
2008-02-06 0:35 ` Bjorn Helgaas
0 siblings, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-05 21:18 UTC (permalink / raw)
To: Len Brown; +Cc: linux-acpi, Matthew Garrett, Alexey Starikovskiy, linux-ia64
On Tuesday 05 February 2008 21:03:30 Len Brown wrote:
> On Monday 04 February 2008 21:16, Carlos Corbacho wrote:
> > 2) Do you know of any IA64 systems using ACPI-WMI? At the moment, WMI
> > will work on x86 and hopefully IA64, but if it turns out that no vendor
> > has or ever will implement ACPI-WMI on IA64, then we could safely drop it
> > there.
>
> I recommend skipping WMI on IA64 unless/until a real need to support it
> emerges. There are no WMI extensions on the Intel Tiger or Hitachi Fusion
> IA64 boxes I have access to.
Ok. The only other vendors I really have in mind are HP and Fujitsu, since
both those vendors use WMI on their laptops, so they would be my most likely
candidates to have WMI on an IA64 box (if anyone actually does use WMI on
IA64).
Len:
You can either add a 'depends on X86' to WMI when you apply patch #1 from the
second WMI series; or I can send another patch out later on top of this
series to do this, once you replace the old patches in acpi-test - whichever
is easiest for you)
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 0/5] WMI patches for acpi-test (v2)
2008-02-05 21:18 ` Carlos Corbacho
@ 2008-02-06 0:35 ` Bjorn Helgaas
2008-02-06 2:03 ` Carlos Corbacho
0 siblings, 1 reply; 31+ messages in thread
From: Bjorn Helgaas @ 2008-02-06 0:35 UTC (permalink / raw)
To: Carlos Corbacho
Cc: Len Brown, linux-acpi, Matthew Garrett, Alexey Starikovskiy,
linux-ia64
On Tuesday 05 February 2008 02:18:01 pm Carlos Corbacho wrote:
> On Tuesday 05 February 2008 21:03:30 Len Brown wrote:
> > On Monday 04 February 2008 21:16, Carlos Corbacho wrote:
> > > 2) Do you know of any IA64 systems using ACPI-WMI? At the moment, WMI
> > > will work on x86 and hopefully IA64, but if it turns out that no vendor
> > > has or ever will implement ACPI-WMI on IA64, then we could safely drop it
> > > there.
> >
> > I recommend skipping WMI on IA64 unless/until a real need to support it
> > emerges. There are no WMI extensions on the Intel Tiger or Hitachi Fusion
> > IA64 boxes I have access to.
>
> Ok. The only other vendors I really have in mind are HP and Fujitsu, since
> both those vendors use WMI on their laptops, so they would be my most likely
> candidates to have WMI on an IA64 box (if anyone actually does use WMI on
> IA64).
How could I tell whether our HP systems use WMI?
Bjorn
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 0/5] WMI patches for acpi-test (v2)
2008-02-06 0:35 ` Bjorn Helgaas
@ 2008-02-06 2:03 ` Carlos Corbacho
2008-02-06 6:30 ` Bjorn Helgaas
0 siblings, 1 reply; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-06 2:03 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: Len Brown, linux-acpi, Matthew Garrett, Alexey Starikovskiy,
linux-ia64
On Wednesday 06 February 2008 00:35:54 Bjorn Helgaas wrote:
> > Ok. The only other vendors I really have in mind are HP and Fujitsu,
> > since both those vendors use WMI on their laptops, so they would be my
> > most likely candidates to have WMI on an IA64 box (if anyone actually
> > does use WMI on IA64).
>
> How could I tell whether our HP systems use WMI?
Do you have any PNP0C14 or *pnp0c14 _HID objects defined in the DSDTs for your
systems? If not, then your systems don't use WMI.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 0/5] WMI patches for acpi-test (v2)
2008-02-06 2:03 ` Carlos Corbacho
@ 2008-02-06 6:30 ` Bjorn Helgaas
0 siblings, 0 replies; 31+ messages in thread
From: Bjorn Helgaas @ 2008-02-06 6:30 UTC (permalink / raw)
To: Carlos Corbacho
Cc: Len Brown, linux-acpi, Matthew Garrett, Alexey Starikovskiy,
linux-ia64
On Tuesday 05 February 2008 07:03:23 pm Carlos Corbacho wrote:
> On Wednesday 06 February 2008 00:35:54 Bjorn Helgaas wrote:
> > > Ok. The only other vendors I really have in mind are HP and Fujitsu,
> > > since both those vendors use WMI on their laptops, so they would be my
> > > most likely candidates to have WMI on an IA64 box (if anyone actually
> > > does use WMI on IA64).
> >
> > How could I tell whether our HP systems use WMI?
>
> Do you have any PNP0C14 or *pnp0c14 _HID objects defined in the DSDTs for your
> systems? If not, then your systems don't use WMI.
I looked at DSDTs for a few HP ia64 systems (rx7620,
rx8640, and superdome), and none has a PNP0C14 object.
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2008-02-02 12:17 [PATCH 0/5] WMI patches for acpi-test Carlos Corbacho
@ 2008-02-02 12:17 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-02-02 12:17 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
v4:
* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
WMID laptops.
v5:
* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
to ensure that the correct value is always set.
v6:
* Fix 'use before initialisation' bug with quirks.
v7
* Fix bug on AMW0 where acer-wmi would exit if a mail LED was not
detected.
* Add Acer Aspire 9110 mail LED support
* Fix section mismatch warnings
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 1109 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1126 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f20c30c..e19b298 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index a9e8faf..7adf02f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..bf49787
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1109 @@
+/*
+ * Acer Laptop WMI 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);
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2008-01-18 23:58 [PATCH 0/5] WMI Carlos Corbacho
@ 2008-01-18 23:58 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-01-18 23:58 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
v4:
* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
WMID laptops.
v5:
* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
to ensure that the correct value is always set.
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 1100 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1117 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..99f0076
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1100 @@
+/*
+ * Acer Laptop WMI 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 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(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 7) & 0x01;
+ 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 & 0x01;
+ return AE_OK;
+ default:
+ err = ec_read(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 2) & 0x01;
+ return AE_OK;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ err = ec_read(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 4) & 0x01;
+ 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);
+
+ 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;
+ 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 __exit 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);
+
+ find_quirks();
+
+ /*
+ * 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 (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+
+ 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");
+ return -ENODEV;
+ }
+ }
+
+ 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);
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2008-01-12 2:20 [PATCH 0/5] WMI Carlos Corbacho
@ 2008-01-12 2:20 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2008-01-12 2:20 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
v4:
* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
WMID laptops.
v5:
* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
to ensure that the correct value is always set.
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 1100 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1117 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..99f0076
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1100 @@
+/*
+ * Acer Laptop WMI 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 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(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 7) & 0x01;
+ 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 & 0x01;
+ return AE_OK;
+ default:
+ err = ec_read(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 2) & 0x01;
+ return AE_OK;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ err = ec_read(0x0A, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 4) & 0x01;
+ 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);
+
+ 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;
+ 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 __exit 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);
+
+ find_quirks();
+
+ /*
+ * 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 (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+
+ 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");
+ return -ENODEV;
+ }
+ }
+
+ 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);
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2007-12-27 15:38 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-27 15:38 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2007-12-27 15:38 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
v4:
* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
WMID laptops.
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 965 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 982 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..41ac574
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,965 @@
+/*
+ * Acer Laptop WMI Extras
+ *
+ * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * Based on acer_acpi:
+ * Copyright (C) 2005-2007 E.M. Smith
+ * Copyright (C) 2007 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 (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 old AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK 0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK 0x31
+
+/*
+ * Method IDs for new 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;
+ u8 max_brightness;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+ if (quirks->mailled != 0)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ if (quirks->brightness != 0)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+
+ if (quirks->bluetooth != 0)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ if (quirks->wireless != 0)
+ interface->capability |= ACER_CAP_WIRELESS;
+
+ if (quirks->max_brightness != 0)
+ max_brightness = quirks->max_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,
+};
+
+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 TravelMate 2490",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {}
+};
+
+/* 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;
+}
+
+/*
+ * Old interface (now known as the AMW0 interface)
+ */
+struct wmab_args {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+};
+
+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)
+{
+ u8 result;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 7) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_WIRELESS:
+ switch (quirks->wireless) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 2) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 4) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ ec_read(0x83, &result);
+ *value = result;
+ return 0;
+ }
+ 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 struct wmi_interface AMW0_interface = {
+ .type = ACER_AMW0,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH
+ ),
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+ .type = ACER_AMW0_V2,
+ .capability = (
+ ACER_CAP_BRIGHTNESS |
+ ACER_CAP_MAILLED
+ ),
+};
+
+/*
+ * 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;
+
+ if (result.length > 0 && result.pointer)
+ 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;
+ 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 */
+ interface->capability |= ACER_CAP_WIRELESS;
+ interface->capability |= ACER_CAP_THREEG;
+
+ 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,
+ .capability = ACER_CAP_BRIGHTNESS,
+};
+
+/*
+ * 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 void acer_led_init(struct device *dev)
+{
+ 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)
+{
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_init(&device->dev);
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_init(&device->dev);
+ return 0;
+}
+
+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;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_bluetooth);
+ if (retval)
+ goto error;
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_threeg);
+ if (retval)
+ goto error;
+ }
+
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_interface);
+ if (retval)
+ goto error;
+
+ return 0;
+
+error:
+ remove_sysfs(acer_platform_device);
+ return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+ printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n",
+ ACER_WMI_VERSION);
+
+ find_quirks();
+
+ /*
+ * Detect which ACPI-WMI interface we're using.
+ */
+ if (wmi_has_guid(AMW0_GUID1)) {
+ interface = &AMW0_interface;
+
+ if (!quirks)
+ return -ENOMEM;
+
+ if (wmi_has_guid(WMID_GUID1)) {
+ interface = &AMW0_V2_interface;
+ } else {
+ if (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+ }
+ } else if (wmi_has_guid(WMID_GUID1)) {
+ interface = &wmid_interface;
+ } else {
+ printk(ACER_ERR "No or unsupported WMI interface, unable to ");
+ printk(KERN_CONT "load.\n");
+ return -ENODEV;
+ }
+
+ if (wmi_has_guid(WMID_GUID2)) {
+ if (ACPI_FAILURE(WMID_set_capabilities())) {
+ printk(ACER_ERR "Unable to detect available devices\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);
+
+ create_sysfs();
+
+ /* 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);
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2007-12-17 0:23 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-17 0:23 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2007-12-17 0:23 UTC (permalink / raw)
To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1
drivers/misc/acer-wmi.c | 1009 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1026 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..ebdf70c
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1009 @@
+/*
+ * Acer Laptop WMI Extras
+ *
+ * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * Based on acer_acpi:
+ * Copyright (C) 2005-2007 E.M. Smith
+ * Copyright (C) 2007 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 (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 EC)
+ */
+#define ACER_AMW0_WRITE 0x9610
+
+/*
+ * Bit masks for the old AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK 0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK 0x31
+
+/*
+ * Method IDs for new 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"
+
+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;
+ u8 max_brightness;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+ if (quirks->mailled != 0)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ if (quirks->brightness != 0)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+
+ if (quirks->bluetooth != 0)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ if (quirks->wireless != 0)
+ interface->capability |= ACER_CAP_WIRELESS;
+
+ if (quirks->max_brightness != 0)
+ max_brightness = quirks->max_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,
+};
+
+static struct quirk_entry quirk_acer_travelmate_5720 = {
+ .max_brightness = 0x9,
+};
+
+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 Extensa 5220",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5220"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {
+ .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 = "Acer TravelMate 5720",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5720"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {}
+};
+
+/* 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;
+}
+
+/*
+ * Old interface (now known as the AMW0 interface)
+ */
+struct wmab_args {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+};
+
+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_bool(bool *value, u32 cap,
+struct wmi_interface *iface)
+{
+ u8 result;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 7) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_WIRELESS:
+ switch (quirks->wireless) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 2) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 4) & 0x01;
+ return 0;
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_bool(bool 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:
+ args.ebx |= ACER_AMW0_MAILLED_MASK;
+ break;
+ case ACER_CAP_WIRELESS:
+ args.ebx |= ACER_AMW0_WIRELESS_MASK;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+
+ /* Actually do the set */
+ return WMAB_execute(&args, NULL);
+}
+
+static acpi_status AMW0_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_read(0x83, value);
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_write(0x83, value);
+ break;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static struct wmi_interface AMW0_interface = {
+ .type = ACER_AMW0,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH
+ ),
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+ .type = ACER_AMW0_V2,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH |
+ ACER_CAP_BRIGHTNESS
+ ),
+};
+
+/*
+ * 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;
+
+ if (result.length > 0 && result.pointer)
+ kfree(result.pointer);
+
+ return status;
+}
+
+static acpi_status WMID_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ acpi_status status;
+ u32 result;
+ u32 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:
+ switch (quirks->mailled) {
+ case 1:
+ ec_read(0x9f, value);
+ *value &= 0x01;
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ 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_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ u32 method_id = 0;
+ char param;
+
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+ break;
+ case ACER_CAP_WIRELESS:
+ method_id = ACER_WMID_SET_WIRELESS_METHODID;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+ break;
+ case ACER_CAP_THREEG:
+ method_id = ACER_WMID_SET_THREEG_METHODID;
+ break;
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ case 1:
+ param = value? 0x92 : 0x93;
+ i8042_command(¶m, 0x1059);
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+
+static struct wmi_interface wmid_interface = {
+ .type = ACER_WMID,
+ .capability = (
+ ACER_CAP_WIRELESS
+ | ACER_CAP_BRIGHTNESS
+ | ACER_CAP_BLUETOOTH
+ | ACER_CAP_THREEG
+ ),
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_bool(bool *value, u32 cap)
+{
+ acpi_status status = AE_BAD_ADDRESS;
+ u8 tmp = 0;
+
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_get_u8(&tmp, cap, interface);
+ *value = (tmp == 1) ? 1 : 0;
+ break;
+ }
+ return status;
+}
+
+static acpi_status set_bool(int value, u32 cap)
+{
+ acpi_status status = AE_BAD_PARAMETER;
+
+ if ((value == 0 || value == 1) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_set_bool(value == 1, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_set_bool(value == 1, cap,
+ interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_set_u8(value == 1, cap, interface);
+ break;
+ }
+ }
+ return status;
+}
+
+static acpi_status get_u8(u8 *value, u32 cap)
+{
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_get_u8(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_get_u8(value, cap, interface);
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+}
+
+static acpi_status set_u8(u8 value, u8 min, u8 max, u32 cap)
+{
+ if ((value >= min && value <= max) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_set_u8(value, cap, interface);
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_set_u8(value, cap, interface);
+ default:
+ return AE_BAD_PARAMETER;
+ }
+ }
+ return AE_BAD_PARAMETER;
+}
+
+/* Each _u8 needs a small wrapper that sets the boundary values */
+static acpi_status set_brightness(u8 value)
+{
+ return set_u8(value, 0, max_brightness, ACER_CAP_BRIGHTNESS);
+}
+
+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_bool(mailled, ACER_CAP_MAILLED);
+ set_bool(wireless, ACER_CAP_WIRELESS);
+ set_bool(bluetooth, ACER_CAP_BLUETOOTH);
+ set_bool(threeg, ACER_CAP_THREEG);
+ set_brightness((u8)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)
+{
+ bool tmp = value;
+ set_bool(tmp, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+ .name = "acer-mail:green",
+ .brightness_set = mail_led_set,
+};
+
+static void acer_led_init(struct device *dev)
+{
+ 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)
+{
+ u8 value;
+ get_u8(&value, ACER_CAP_BRIGHTNESS);
+ return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ set_brightness(bd->props.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) \
+{ \
+ bool result; \
+ acpi_status status = get_bool(&result, cap); \
+ 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) \
+{ \
+ bool tmp = simple_strtoul(buf, NULL, 10); \
+ acpi_status status = set_bool(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)
+{
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_init(&device->dev);
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_init(&device->dev);
+ return 0;
+}
+
+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)
+{
+ bool value;
+ u8 u8value;
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return -ENOMEM;
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ get_bool(&value, ACER_CAP_WIRELESS);
+ data->wireless = value;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ get_bool(&value, ACER_CAP_BLUETOOTH);
+ data->bluetooth = value;
+ }
+
+ if (has_cap(ACER_CAP_MAILLED)) {
+ get_bool(&value, ACER_CAP_MAILLED);
+ data->mailled = value;
+ }
+
+ if (has_cap(ACER_CAP_BRIGHTNESS)) {
+ get_u8(&u8value, 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_bool(data->wireless, ACER_CAP_WIRELESS);
+
+ if (has_cap(ACER_CAP_BLUETOOTH))
+ set_bool(data->bluetooth, ACER_CAP_BLUETOOTH);
+
+ if (has_cap(ACER_CAP_THREEG))
+ set_bool(data->threeg, ACER_CAP_THREEG);
+
+ if (has_cap(ACER_CAP_MAILLED))
+ set_bool(data->mailled, ACER_CAP_MAILLED);
+
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ set_brightness((u8)data->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;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_bluetooth);
+ if (retval)
+ goto error;
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_threeg);
+ if (retval)
+ goto error;
+ }
+
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_interface);
+ if (retval)
+ goto error;
+
+ return 0;
+
+error:
+ remove_sysfs(acer_platform_device);
+ return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+ printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n",
+ ACER_WMI_VERSION);
+
+ find_quirks();
+
+ /*
+ * Detect which ACPI-WMI interface we're using.
+ */
+ if (wmi_has_guid(AMW0_GUID1)) {
+ interface = &AMW0_interface;
+
+ if (!quirks)
+ return -ENOMEM;
+
+ if (wmi_has_guid(WMID_GUID1)) {
+ interface = &AMW0_V2_interface;
+ } else {
+ if (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+ }
+ } else if (wmi_has_guid(WMID_GUID1)) {
+ interface = &wmid_interface;
+ } else {
+ 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);
+
+ create_sysfs();
+
+ /* 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);
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
2007-12-12 2:01 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-12 2:08 ` Carlos Corbacho
0 siblings, 0 replies; 31+ messages in thread
From: Carlos Corbacho @ 2007-12-12 2:08 UTC (permalink / raw)
To: linux-acpi; +Cc: lenb, Matthew Garrett
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.
v1:
* Initial release
v2:
* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
work is available)
* Checkpatch fixes
v3:
* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220
Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
---
drivers/misc/Kconfig | 16 +
drivers/misc/Makefile | 1 +
drivers/misc/acer-wmi.c | 1009 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1026 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/acer-wmi.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 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 Laptop WMI-ACPI 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
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ 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_LKDTM) += lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..ebdf70c
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1009 @@
+/*
+ * Acer Laptop WMI Extras
+ *
+ * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * Based on acer_acpi:
+ * Copyright (C) 2005-2007 E.M. Smith
+ * Copyright (C) 2007 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 (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 EC)
+ */
+#define ACER_AMW0_WRITE 0x9610
+
+/*
+ * Bit masks for the old AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK 0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK 0x31
+
+/*
+ * Method IDs for new 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"
+
+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;
+ u8 max_brightness;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+ if (quirks->mailled != 0)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ if (quirks->brightness != 0)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+
+ if (quirks->bluetooth != 0)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ if (quirks->wireless != 0)
+ interface->capability |= ACER_CAP_WIRELESS;
+
+ if (quirks->max_brightness != 0)
+ max_brightness = quirks->max_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,
+};
+
+static struct quirk_entry quirk_acer_travelmate_5720 = {
+ .max_brightness = 0x9,
+};
+
+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 Extensa 5220",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5220"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {
+ .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 = "Acer TravelMate 5720",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5720"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {}
+};
+
+/* 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;
+}
+
+/*
+ * Old interface (now known as the AMW0 interface)
+ */
+struct wmab_args {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+};
+
+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_bool(bool *value, u32 cap,
+struct wmi_interface *iface)
+{
+ u8 result;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 7) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_WIRELESS:
+ switch (quirks->wireless) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 2) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 4) & 0x01;
+ return 0;
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_bool(bool 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:
+ args.ebx |= ACER_AMW0_MAILLED_MASK;
+ break;
+ case ACER_CAP_WIRELESS:
+ args.ebx |= ACER_AMW0_WIRELESS_MASK;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+
+ /* Actually do the set */
+ return WMAB_execute(&args, NULL);
+}
+
+static acpi_status AMW0_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_read(0x83, value);
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_write(0x83, value);
+ break;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static struct wmi_interface AMW0_interface = {
+ .type = ACER_AMW0,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH
+ ),
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+ .type = ACER_AMW0_V2,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH |
+ ACER_CAP_BRIGHTNESS
+ ),
+};
+
+/*
+ * 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;
+
+ if (result.length > 0 && result.pointer)
+ kfree(result.pointer);
+
+ return status;
+}
+
+static acpi_status WMID_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ acpi_status status;
+ u32 result;
+ u32 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:
+ switch (quirks->mailled) {
+ case 1:
+ ec_read(0x9f, value);
+ *value &= 0x01;
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ 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_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ u32 method_id = 0;
+ char param;
+
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+ break;
+ case ACER_CAP_WIRELESS:
+ method_id = ACER_WMID_SET_WIRELESS_METHODID;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+ break;
+ case ACER_CAP_THREEG:
+ method_id = ACER_WMID_SET_THREEG_METHODID;
+ break;
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ case 1:
+ param = value? 0x92 : 0x93;
+ i8042_command(¶m, 0x1059);
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+
+static struct wmi_interface wmid_interface = {
+ .type = ACER_WMID,
+ .capability = (
+ ACER_CAP_WIRELESS
+ | ACER_CAP_BRIGHTNESS
+ | ACER_CAP_BLUETOOTH
+ | ACER_CAP_THREEG
+ ),
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_bool(bool *value, u32 cap)
+{
+ acpi_status status = AE_BAD_ADDRESS;
+ u8 tmp = 0;
+
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_get_u8(&tmp, cap, interface);
+ *value = (tmp == 1) ? 1 : 0;
+ break;
+ }
+ return status;
+}
+
+static acpi_status set_bool(int value, u32 cap)
+{
+ acpi_status status = AE_BAD_PARAMETER;
+
+ if ((value == 0 || value == 1) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_set_bool(value == 1, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_set_bool(value == 1, cap,
+ interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_set_u8(value == 1, cap, interface);
+ break;
+ }
+ }
+ return status;
+}
+
+static acpi_status get_u8(u8 *value, u32 cap)
+{
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_get_u8(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_get_u8(value, cap, interface);
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+}
+
+static acpi_status set_u8(u8 value, u8 min, u8 max, u32 cap)
+{
+ if ((value >= min && value <= max) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_set_u8(value, cap, interface);
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_set_u8(value, cap, interface);
+ default:
+ return AE_BAD_PARAMETER;
+ }
+ }
+ return AE_BAD_PARAMETER;
+}
+
+/* Each _u8 needs a small wrapper that sets the boundary values */
+static acpi_status set_brightness(u8 value)
+{
+ return set_u8(value, 0, max_brightness, ACER_CAP_BRIGHTNESS);
+}
+
+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_bool(mailled, ACER_CAP_MAILLED);
+ set_bool(wireless, ACER_CAP_WIRELESS);
+ set_bool(bluetooth, ACER_CAP_BLUETOOTH);
+ set_bool(threeg, ACER_CAP_THREEG);
+ set_brightness((u8)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)
+{
+ bool tmp = value;
+ set_bool(tmp, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+ .name = "acer-mail:green",
+ .brightness_set = mail_led_set,
+};
+
+static void acer_led_init(struct device *dev)
+{
+ 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)
+{
+ u8 value;
+ get_u8(&value, ACER_CAP_BRIGHTNESS);
+ return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ set_brightness(bd->props.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) \
+{ \
+ bool result; \
+ acpi_status status = get_bool(&result, cap); \
+ 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) \
+{ \
+ bool tmp = simple_strtoul(buf, NULL, 10); \
+ acpi_status status = set_bool(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)
+{
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_init(&device->dev);
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_init(&device->dev);
+ return 0;
+}
+
+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)
+{
+ bool value;
+ u8 u8value;
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return -ENOMEM;
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ get_bool(&value, ACER_CAP_WIRELESS);
+ data->wireless = value;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ get_bool(&value, ACER_CAP_BLUETOOTH);
+ data->bluetooth = value;
+ }
+
+ if (has_cap(ACER_CAP_MAILLED)) {
+ get_bool(&value, ACER_CAP_MAILLED);
+ data->mailled = value;
+ }
+
+ if (has_cap(ACER_CAP_BRIGHTNESS)) {
+ get_u8(&u8value, 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_bool(data->wireless, ACER_CAP_WIRELESS);
+
+ if (has_cap(ACER_CAP_BLUETOOTH))
+ set_bool(data->bluetooth, ACER_CAP_BLUETOOTH);
+
+ if (has_cap(ACER_CAP_THREEG))
+ set_bool(data->threeg, ACER_CAP_THREEG);
+
+ if (has_cap(ACER_CAP_MAILLED))
+ set_bool(data->mailled, ACER_CAP_MAILLED);
+
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ set_brightness((u8)data->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;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_bluetooth);
+ if (retval)
+ goto error;
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_threeg);
+ if (retval)
+ goto error;
+ }
+
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_interface);
+ if (retval)
+ goto error;
+
+ return 0;
+
+error:
+ remove_sysfs(acer_platform_device);
+ return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+ printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n",
+ ACER_WMI_VERSION);
+
+ find_quirks();
+
+ /*
+ * Detect which ACPI-WMI interface we're using.
+ */
+ if (wmi_has_guid(AMW0_GUID1)) {
+ interface = &AMW0_interface;
+
+ if (!quirks)
+ return -ENOMEM;
+
+ if (wmi_has_guid(WMID_GUID1)) {
+ interface = &AMW0_V2_interface;
+ } else {
+ if (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+ }
+ } else if (wmi_has_guid(WMID_GUID1)) {
+ interface = &wmid_interface;
+ } else {
+ 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);
+
+ create_sysfs();
+
+ /* 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);
--
1.5.3.4
^ permalink raw reply related [flat|nested] 31+ messages in thread