All of lore.kernel.org
 help / color / mirror / Atom feed
From: Carlos Corbacho <cathectic@gmail.com>
To: linux-acpi@vger.kernel.org
Subject: [RFC][PATCH 1/5] ACPI: acer_acpi: Add acer_acpi driver
Date: Tue, 17 Jul 2007 15:45:53 +0100	[thread overview]
Message-ID: <200707171545.54037.cathectic@gmail.com> (raw)
In-Reply-To: <200707171544.57004.cathectic@gmail.com>

Add the acer_acpi driver - this driver supports many recent (2005 and later)
Acer laptops that provide a PNP0C14 _HID device through ACPI. It enables
controlling wireless, bluetooth, backlight and mail LED on supported models.

Signed-off-by: Carlos Corbacho <cathectic@gmail.com>
---
 drivers/misc/acer_acpi.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1110 insertions(+), 0 deletions(-)

diff --git a/drivers/misc/acer_acpi.c b/drivers/misc/acer_acpi.c
new file mode 100644
index 0000000..954c7d9
--- /dev/null
+++ b/drivers/misc/acer_acpi.c
@@ -0,0 +1,1110 @@
+/*
+ *  acer_acpi.c - Acer Laptop ACPI Extras
+ *
+ *
+ *  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
+ *
+ *
+ *  The devolpment page for this driver is located at
+ *  http://code.google.com/p/aceracpi
+ *
+ *  Credits:
+ *
+ *  The authors of laptop ACPI modules in the kernel
+ *              - their work was an inspiration and a source of good code
+ *  Olaf Tauber
+ *              - developer of acerhk, the inspiration to solve the 64-bit
+ *                driver problem for [Mark Smith's] Aspire 5024.
+ *  Mathieu Segaud
+ *              - solved the ACPI problem that needed a double-modprobe in
+ *                acer_acpi 0.2 and below.
+ *  Jim Ramsay
+ *              - Figured out and added support for WMID, added generic
+ *                interface code
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/uaccess.h>
+#include <linux/preempt.h>
+#include <linux/io.h>
+#include <linux/dmi.h>
+
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Mark Smith, Carlos Corbacho");
+MODULE_DESCRIPTION("Acer Laptop ACPI Extras Driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Defining this enables the 3g interface on the new WMID interface.
+ *
+ * However, there are many reports of this not working (and no reports of it
+ * working), so this is for experienced users only.
+ */
+#undef EXPERIMENTAL_INTERFACES
+
+#define ACER_LOGPREFIX "acer_acpi: "
+
+#define DEBUG(level, message...) \
+	do { \
+		if (debug >= level) \
+		printk(KERN_DEBUG ACER_LOGPREFIX message); \
+	} while (0);
+
+/*
+ * On the 5580, brightness values range from 0x0 to 0xf, inclusive.
+ * This may vary on other machines or other interfaces.
+ */
+#define ACER_MAX_BRIGHTNESS 0xf
+
+/*
+ * These laptops are known to require a keyboard quirk to make their
+ * multimedia keys emit scancodes
+ */
+static struct dmi_system_id keyboard_dmi_table[] = {
+	{
+		.ident = "Acer Aspire 5680",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+		},
+	},
+	{ }
+};
+
+/*
+ * Keyboard controller ports
+ */
+#define ACER_KBD_STATUS_REG	0x64	/* Status register (R) */
+#define ACER_KBD_CNTL_REG	0x64	/* Controller command register (W) */
+#define ACER_KBD_DATA_REG	0x60	/* Keyboard data register (R/W) */
+
+/*
+ * AMW0 EC Magic Number
+ * This number is required for writing to ACPI
+ * (it's also used in acerhk when directly accessing the EC)
+ */
+#define ACER_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
+ * These could be different for other untested machines
+ */
+#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 paths
+ *
+ * TODO: It may be possbile to autodetect these, since these are all at HID
+ * PNP0C14
+ */
+#define AMW0_METHOD		"\\_SB_.AMW0.WMAB"
+#define AMW0_GETDATA		"\\_SB_.AMW0._WED"
+
+#define WMID_METHOD		"\\_SB.WMID.WMBA"
+#define WMID_GETDATA		"\\_SB.WMID._WED"
+
+/*
+ * 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
+ */
+#define ACER_AMW0 (1<<0)
+#define ACER_WMID (1<<1)
+
+/*
+ * Presumed start states -
+ * For the AMW0 interface, there is no way to know for certain what the start
+ * state is for any of the devices (ACPI does not provide any methods or store
+ * this anywhere, and a device state will persist across a reboot).
+ *
+ * The driver therefore changes from the unknown state to a known state (off)
+ * (Acer software just waits for the user to press the device button, as the
+ * scancode for the button changes when the device state is changed).
+ *
+ * Also, we can't yet tell which features are enabled or disabled on a specific
+ * model, just ranges; but we don't have enough information on the models, so we
+ * currently unconditionally enable all supported devices on a given interface.
+ *
+ * Basically the code works like this:
+ *   - On init, any values specified on the commandline are set.
+ *   - For interfaces where the current values cannot be detected and which
+ *     have not been set on the commandline, we set them to some sane default
+ *     (disabled)
+ *
+ * See AMW0_init and acer_commandline_init
+ */
+
+#define ACER_DEFAULT_WIRELESS  0
+#define ACER_DEFAULT_BLUETOOTH 0
+#define ACER_DEFAULT_MAILLED   0
+#define ACER_DEFAULT_THREEG    0
+#define ACER_DEFAULT_BRIGHTNESS ACER_MAX_BRIGHTNESS
+
+static int wireless = -1;
+static int bluetooth = -1;
+static int mailled = -1;
+static int brightness = -1;
+static int threeg = -1;
+static int debug;
+
+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(debug, int, 0664);
+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(debug, "Debugging verbosity level (0=least 2=most)");
+
+static int is_valid_acpi_path(const char *methodName)
+{
+	acpi_handle handle;
+	acpi_status status;
+
+	status = acpi_get_handle(NULL, (char *)methodName, &handle);
+	return ACPI_SUCCESS(status);
+}
+
+/* Each low-level interface must define at least some of the following */
+struct Interface {
+	/*
+	 * The ACPI device type
+	 */
+	uint32_t type;
+
+
+	/*
+	 * The capabilities this interface provides
+	 * In the future, these can be removed/added at runtime when we have a
+	 * way of detecting what capabilities are /actually/ present on an
+	 * interface
+	 */
+	uint32_t capability;
+
+	/*
+	 * Initializes an interface, should allocate the interface-specific
+	 * data
+	 */
+	void (*init) (struct Interface *);
+
+	/*
+	 * Frees an interface, should free the interface-specific data
+	 */
+	void (*free) (struct Interface *);
+
+	/*
+	 * Gets and sets various data types.
+	 *   First paramater:  Value to set, or pointer to place got value into
+	 *   Second parameter: Specific capability being requested
+	 *   Third paramater: Pointer to this interface
+	 */
+	acpi_status (*get_bool) (bool *, uint32_t, struct Interface *);
+	acpi_status (*set_bool) (bool, uint32_t, struct Interface *);
+	acpi_status (*get_u8) (uint8_t *, uint32_t, struct Interface *);
+	acpi_status (*set_u8) (uint8_t, uint32_t, struct Interface *);
+
+	/*
+	 * Interface-specific private data member.  Must *not* be touched by
+	 * anyone outside of this struct
+	 */
+	void *data;
+};
+
+/* The static interface pointer, points to the currently detected interface */
+static struct Interface *interface;
+
+/*
+ * General interface convenience methods
+ */
+
+static bool has_cap(uint32_t cap)
+{
+	if ((interface->capability & cap) != 0) {
+		return 1;
+	}
+	return 0;
+}
+
+static void interface_free(struct Interface *iface)
+{
+	/* Free our private data structure */
+	kfree(iface->data);
+}
+
+/* These *_via_u8 use the interface's *_u8 methods to emulate other gets/sets */
+static acpi_status get_bool_via_u8(bool *value, uint32_t cap,
+	struct Interface *iface)
+{
+	acpi_status status;
+	uint8_t result;
+
+	status = iface->get_u8(&result, cap, iface);
+
+	if (ACPI_SUCCESS(status))
+		*value = (result != 0);
+
+	return status;
+}
+
+static acpi_status set_bool_via_u8(bool value, uint32_t cap,
+	struct Interface *iface)
+{
+	uint8_t v = value ? 1 : 0;
+
+	return iface->set_u8(v, cap, iface);
+}
+
+/* General wrapper around the ACPI call */
+static acpi_status
+WMI_execute(char *methodPath, uint32_t methodId, const struct acpi_buffer *in,
+	struct acpi_buffer *out)
+{
+	struct acpi_object_list input;
+	union acpi_object params[3];
+	acpi_status status = AE_OK;
+
+	/* WMI calling convention:
+	 *  methodPath( instance, methodId, input_buffer )
+	 *    - instance is always 1, since there's only this module
+	 *    - methodId is the method number within the current method group.
+	 *    - Input buffer is ignored for read-only commands
+	 *    - May return a buffer of results (optional)
+	 */
+	input.count = 3;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = 0x01;
+	params[1].type = ACPI_TYPE_INTEGER;
+	params[1].integer.value = methodId;
+	params[2].type = ACPI_TYPE_BUFFER;
+	params[2].buffer.length = in->length;
+	params[2].buffer.pointer = in->pointer;
+
+	DEBUG(2, "Doing %s( 1, %u, [%llu-byte buffer] )\n", methodPath,
+	methodId, (uint64_t)in->length);
+
+	status = acpi_evaluate_object(NULL, methodPath, &input, out);
+
+	DEBUG(2, "  Execution status: %d\n", status);
+	DEBUG(2, "  Result: %llu bytes\n", (uint64_t)(out ? out->length : 0) );
+
+	return status;
+}
+
+/*
+ * Keyboard quirk
+ */
+
+/*
+ * Wait for the keyboard controller to become ready
+ */
+static int wait_kbd_write(void)
+{
+	int i = 0;
+	while ((inb(ACER_KBD_STATUS_REG) & 0x02) && (i < 10000)) {
+		udelay(50);
+		i++;
+	}
+	return -(i == 10000);
+}
+
+static void set_keyboard_quirk(void)
+{
+	preempt_disable();
+	if (!wait_kbd_write()) {
+		outb(0x59, ACER_KBD_CNTL_REG);
+		if (!wait_kbd_write())
+			outb(0x90, ACER_KBD_DATA_REG);
+	} else {
+		printk(KERN_ERR ACER_LOGPREFIX "Unable to apply keyboard \
+		quirk\n");
+	}
+	preempt_enable_no_resched();
+}
+
+/*
+ * Check if this system requires the keyboard quirk to enable multimedia keys
+ */
+static bool keyboard_quirk(void)
+{
+	if (dmi_check_system(keyboard_dmi_table))
+		return 1;
+	return 0;
+}
+
+/*
+ * Old interface (now known as the AMW0 interface)
+ */
+struct WMAB_args {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+};
+
+struct AMW0_Data {
+	int mailled;
+	int wireless;
+	int bluetooth;
+};
+
+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_execute( AMW0_METHOD, 1, &input, result);
+	DEBUG(2, "  Args: 0x%08x 0x%08x 0x%08x 0x%08x\n", regbuf->eax,
+		regbuf->ebx, regbuf->ecx, regbuf->edx );
+
+	return status;
+}
+
+static void AMW0_init(struct Interface *iface)
+{
+	struct AMW0_Data *data;
+
+	/* Allocate our private data structure */
+	iface->data = kmalloc(sizeof(struct AMW0_Data), GFP_KERNEL);
+	data = (struct AMW0_Data *)iface->data;
+
+	/*
+	 * If the commandline doesn't specify these, we need to force them to
+	 * the default values
+	 */
+	if (mailled == -1)
+		mailled = ACER_DEFAULT_MAILLED;
+	if (wireless == -1)
+		wireless = ACER_DEFAULT_WIRELESS;
+	if (bluetooth == -1)
+		bluetooth = ACER_DEFAULT_BLUETOOTH;
+
+	/*
+	 * Set the cached "current" values to impossible ones so that
+	 * acer_commandline_init will definitely set them.
+	 */
+	data->wireless = data->mailled = data->bluetooth = -1;
+}
+
+static acpi_status AMW0_get_bool(bool *value, uint32_t cap,
+	struct Interface *iface)
+{
+	struct AMW0_Data *data = iface->data;
+
+	/*
+	 * Currently no way to query the state, so just return the cached
+	 * value
+	 */
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		*value = data->mailled;
+		break;
+	case ACER_CAP_WIRELESS:
+		*value = data->wireless;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		*value = data->bluetooth;
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_bool(bool value, uint32_t cap,
+	struct Interface *iface)
+{
+	struct WMAB_args args;
+	acpi_status status;
+
+	args.eax = ACER_WRITE;
+	args.ebx = value ? (1<<8) : 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 */
+	status = WMAB_execute(&args, NULL);
+
+	/*
+	 * Currently no way to query the state, so cache the new value on
+	 * success
+	 */
+	if (ACPI_SUCCESS(status)) {
+		struct AMW0_Data *data = iface->data;
+		switch (cap) {
+		case ACER_CAP_MAILLED:
+			data->mailled = value;
+			break;
+		case ACER_CAP_WIRELESS:
+			data->wireless = value;
+			break;
+		case ACER_CAP_BLUETOOTH:
+			data->bluetooth = value;
+			break;
+		}
+	}
+
+	return status;
+}
+
+static struct Interface AMW0_interface = {
+	.type = ACER_AMW0,
+	.capability = (
+		ACER_CAP_MAILLED |
+		ACER_CAP_WIRELESS |
+		ACER_CAP_BLUETOOTH
+	),
+	.init = AMW0_init,
+	.free = interface_free,
+	.get_bool = AMW0_get_bool,
+	.set_bool = AMW0_set_bool,
+};
+
+/*
+ * New interface (The WMID interface)
+ */
+struct WMID_Data {
+	int mailled;
+	int wireless;
+	int bluetooth;
+#ifdef EXPERIMENTAL_INTERFACES
+	int threeg;
+#endif
+	int brightness;
+};
+
+static void WMID_init(struct Interface *iface)
+{
+	struct WMID_Data *data;
+
+	/* Allocate our private data structure */
+	iface->data = kmalloc(sizeof(struct WMID_Data), GFP_KERNEL);
+	data = (struct WMID_Data *)iface->data;
+}
+
+static acpi_status WMI_execute_uint32(uint32_t methodId, uint32_t in,
+	uint32_t *out)
+{
+	struct acpi_buffer input = {(acpi_size)sizeof(uint32_t), (void *)(&in)};
+	struct acpi_buffer result = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	uint32_t tmp;
+	acpi_status status;
+
+	status = WMI_execute(WMID_METHOD, methodId, &input, &result);
+	DEBUG(2, "  In: 0x%08x\n", in);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *)result.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(uint32_t)) {
+		tmp = * ((uint32_t *)obj->buffer.pointer);
+		DEBUG(2, "  Out: 0x%08x\n", tmp);
+	} else {
+		tmp = 0;
+		if (obj) {
+			DEBUG(2, "  Got unexpected result of type %d\n",
+			obj->type);
+		} else {
+			DEBUG(2, "  Got unexpected null result\n");
+		}
+	}
+
+	if (out)
+		*out = tmp;
+
+	if (result.length > 0 && result.pointer)
+		kfree(result.pointer);
+
+	return status;
+}
+
+static acpi_status WMID_get_u8(uint8_t *value, uint32_t cap,
+	struct Interface *iface)
+{
+	acpi_status status;
+	uint32_t result;
+	uint32_t methodId = 0;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		methodId = ACER_WMID_GET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		methodId = ACER_WMID_GET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		methodId = ACER_WMID_GET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		methodId = ACER_WMID_GET_THREEG_METHODID;
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	status = WMI_execute_uint32(methodId, 0, &result);
+
+	if (ACPI_SUCCESS(status))
+		*value = (uint8_t)result;
+
+	return status;
+}
+
+static acpi_status WMID_set_u8(uint8_t value, uint32_t cap,
+	struct Interface *iface)
+{
+	uint32_t methodId = 0;
+
+	switch (cap) {
+	case ACER_CAP_BRIGHTNESS:
+		methodId = ACER_WMID_SET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_WIRELESS:
+		methodId = ACER_WMID_SET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		methodId = ACER_WMID_SET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		methodId = ACER_WMID_SET_THREEG_METHODID;
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return WMI_execute_uint32(methodId, (uint32_t)value, NULL);
+}
+
+
+static struct Interface WMID_interface = {
+	.type = ACER_WMID,
+	.capability = (
+		ACER_CAP_WIRELESS
+		| ACER_CAP_BRIGHTNESS
+		| ACER_CAP_BLUETOOTH
+#ifdef EXPERIMENTAL_INTERFACES
+		| ACER_CAP_THREEG
+#endif
+	),
+	.init = WMID_init,
+	.free = interface_free,
+	.get_bool = get_bool_via_u8,
+	.set_bool = set_bool_via_u8,
+	.get_u8 = WMID_get_u8,
+	.set_u8 = WMID_set_u8,
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_bool(bool *value, uint32_t cap)
+{
+	acpi_status status = AE_BAD_ADDRESS;
+	if (interface->get_bool)
+		status = interface->get_bool(value, cap, interface);
+	return status;
+}
+
+static acpi_status set_bool(int value, uint32_t cap)
+{
+	acpi_status status = AE_BAD_PARAMETER;
+	if ((value == 0 || value == 1) &&
+			(interface->capability & cap)) {
+		if (interface->set_bool)
+			status = interface->set_bool(value == 1, cap,
+			interface);
+	}
+	return status;
+}
+
+static acpi_status get_u8(uint8_t *value, uint32_t cap)
+{
+	acpi_status status = AE_BAD_ADDRESS;
+	if (interface->get_u8)
+		status = interface->get_u8(value, cap, interface);
+	return status;
+}
+
+static acpi_status set_u8(uint8_t value, uint8_t min, uint8_t max, uint32_t cap)
+{
+	acpi_status status = AE_BAD_PARAMETER;
+	if ((value >= min && value <= max) &&
+			(interface->capability & cap) ) {
+		if (interface->get_u8) {
+			/* If possible, only set if the value has changed */
+			uint8_t actual;
+			status = interface->get_u8(&actual, cap, interface);
+			if (ACPI_SUCCESS(status) && actual == value)
+				return status;
+		}
+		if (interface->set_u8)
+			status = interface->set_u8(value, cap, interface);
+	}
+	return status;
+}
+
+/* Each _u8 needs a small wrapper that sets the boundary values */
+static acpi_status set_brightness(uint8_t value)
+{
+	return set_u8(value, 0, ACER_MAX_BRIGHTNESS, ACER_CAP_BRIGHTNESS);
+}
+
+static void acpi_commandline_init(void)
+{
+	DEBUG(1, "Commandline args: mailled(%d) wireless(%d) bluetooth(%d) \
+		brightness(%d)\n",
+		mailled, wireless, bluetooth, brightness);
+
+	/*
+	 * 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((uint8_t)brightness);
+}
+
+/*
+ * Backlight device (UNTESTED!)
+ */
+static struct backlight_device *acer_backlight_device;
+
+static int read_brightness(struct backlight_device *bd)
+{
+	uint8_t 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_backlight_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int acer_backlight_init(struct device *dev)
+{
+	struct backlight_device *bd;
+
+	DEBUG(1, "Loading backlight driver\n");
+	bd = backlight_device_register("acer_acpi", dev,
+				       NULL, &acer_backlight_ops);
+	if (IS_ERR(bd)) {
+		printk(KERN_ERR ACER_LOGPREFIX
+		       "Could not register Acer backlight device\n");
+		acer_backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+
+	acer_backlight_device = bd;
+
+	bd->props.max_brightness = ACER_MAX_BRIGHTNESS;
+	bd->props.brightness = read_brightness(NULL);
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void acer_backlight_exit(void)
+{
+	backlight_device_unregister(acer_backlight_device);
+}
+
+/*
+ * 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_acpi:mail",
+	.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);
+}
+
+/*
+ * Platform device
+ */
+
+#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" ); \
+} \
+\
+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);
+#ifdef EXPERIMENTAL_INTERFACES
+show_set_bool(threeg, ACER_CAP_THREEG);
+#endif
+
+static struct platform_driver acer_platform_driver = {
+	.driver = {
+		.name = "acer_acpi",
+		.owner = THIS_MODULE,
+		}
+};
+
+static struct platform_device *acer_platform_device;
+
+static int remove_sysfs(struct platform_device *device)
+{
+	#define remove_device_file(value, cap) \
+	do { \
+		if (has_cap(cap)) \
+			device_remove_file(&device->dev, &dev_attr_##value); \
+	} while (0);
+
+	remove_device_file(wireless, ACER_CAP_WIRELESS);
+	remove_device_file(bluetooth, ACER_CAP_BLUETOOTH);
+#ifdef EXPERIMENTAL_INTERFACES
+	remove_device_file(threeg, ACER_CAP_THREEG);
+#endif
+	return 0;
+}
+
+static int acer_platform_add(void)
+{
+	int retval = -ENOMEM;
+	platform_driver_register(&acer_platform_driver);
+
+	acer_platform_device = platform_device_alloc("acer_acpi", -1);
+
+	platform_device_add(acer_platform_device);
+
+	#define add_device_file(value, cap) \
+	do { \
+		if (has_cap(cap)) {\
+			retval = device_create_file(\
+			&acer_platform_device->dev, &dev_attr_##value);\
+			if (retval)\
+				goto error;\
+		} \
+	} while (0);
+
+	add_device_file(wireless, ACER_CAP_WIRELESS);
+	add_device_file(bluetooth, ACER_CAP_BLUETOOTH);
+#ifdef EXPERIMENTAL_INTERFACES
+	add_device_file(threeg, ACER_CAP_THREEG);
+#endif
+
+	return 0;
+
+error:
+	remove_sysfs(acer_platform_device);
+	return retval;
+}
+
+static void acer_platform_remove(void)
+{
+	remove_sysfs(acer_platform_device);
+	platform_device_del(acer_platform_device);
+	platform_driver_unregister(&acer_platform_driver);
+}
+
+/*
+ * ACPI driver
+ */
+static int acer_acpi_add(struct acpi_device *device)
+{
+	struct device *dev = acpi_get_physical_device(device->handle);
+	acer_platform_add();
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_init(dev);
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_init(dev);
+	return 0;
+}
+
+static int acer_acpi_remove(struct acpi_device *device, int type)
+{
+	acer_platform_remove();
+
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_exit();
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_exit();
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int acer_acpi_suspend(struct acpi_device *device, pm_message_t state)
+{
+	/*
+	 * WMID fix for suspend-to-disk - save all current states now so we can
+	 * restore them on resume
+	 */
+	bool value;
+	uint8_t u8value;
+
+	#define save_bool_device(device, cap) \
+	do { \
+		if (has_cap(cap)) {\
+			get_bool(&value, cap);\
+			data->device = value;\
+		} \
+	} while (0);
+
+	#define save_u8_device(device, cap) \
+	do { \
+		if (has_cap(cap)) {\
+			get_u8(&u8value, cap);\
+			data->device = u8value;\
+		} \
+	} while (0);
+
+	if (interface->type == ACER_WMID) {
+		struct WMID_Data *data = interface->data;
+		save_bool_device(wireless, ACER_CAP_WIRELESS);
+		save_bool_device(bluetooth, ACER_CAP_BLUETOOTH);
+#ifdef EXPERIMENTAL_INTERFACES
+		save_bool_device(threeg, ACER_CAP_THREEG);
+#endif
+		save_u8_device(brightness, ACER_CAP_BRIGHTNESS);
+	}
+
+	return 0;
+}
+
+static int acer_acpi_resume(struct acpi_device *device)
+{
+	#define restore_bool_device(device, cap) \
+	do { \
+		if (has_cap((cap)))\
+			set_bool(data->device, (cap));\
+	} while (0);
+
+	/*
+	 * We must _always_ restore AMW0's values, otherwise the values
+	 * after suspend-to-disk are wrong
+	 */
+	if (interface->type == ACER_AMW0) {
+		struct AMW0_Data *data = interface->data;
+
+		restore_bool_device(wireless, ACER_CAP_WIRELESS);
+		restore_bool_device(bluetooth, ACER_CAP_BLUETOOTH);
+		restore_bool_device(mailled, ACER_CAP_MAILLED);
+	} else if (interface->type == ACER_WMID) {
+		struct WMID_Data *data = interface->data;
+
+		if (has_cap(ACER_CAP_BRIGHTNESS))
+			set_brightness((uint8_t)data->brightness);
+#ifdef EXPERIMENTAL_DEVICES
+		restore_bool_device(threeg, THREEG);
+#endif
+		restore_bool_device(wireless, ACER_CAP_WIRELESS);
+		restore_bool_device(bluetooth, ACER_CAP_BLUETOOTH);
+	}
+
+	/* Check if this laptop requires the keyboard quirk */
+	if (keyboard_quirk()) {
+		set_keyboard_quirk();
+		printk(KERN_INFO ACER_LOGPREFIX "Setting keyboard quirk to \
+		enable multimedia keys\n");
+	}
+
+	return 0;
+}
+#else
+#define acer_acpi_suspend NULL
+#define acer_acpi_resume NULL
+#endif
+
+static struct acpi_driver acer_acpi_driver = {
+	.name = "acer_acpi",
+	.class = "acer",
+	.ids = "PNP0C14",
+	.ops = {
+		.add = acer_acpi_add,
+		.remove = acer_acpi_remove,
+		.suspend = acer_acpi_suspend,
+		.resume = acer_acpi_resume,
+		},
+};
+
+static int __init acer_acpi_init(void)
+{
+	acpi_status status = AE_OK;
+
+	printk(KERN_INFO ACER_LOGPREFIX "Acer Laptop ACPI Extras\n");
+	if (acpi_disabled) {
+		printk(KERN_ERR ACER_LOGPREFIX "ACPI Disabled, unable to \
+		load.\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * Detect which WMI interface we're using.
+	 *
+	 * TODO: This could be more dynamic, and perhaps done in part by the
+	 *       acpi_bus driver?
+	 */
+	if (is_valid_acpi_path(AMW0_METHOD)) {
+		DEBUG(0, "Detected Acer AMW0 interface\n");
+		interface = &AMW0_interface;
+		/* .ids is case sensitive - and AMW0 has a mixed case device */
+		acer_acpi_driver.ids = "pnp0c14";
+	} else if (is_valid_acpi_path(WMID_METHOD)) {
+		DEBUG(0, "Detected Acer WMID interface\n");
+		interface = &WMID_interface;
+	} else {
+		printk(KERN_ERR ACER_LOGPREFIX "No or unsupported WMI \
+		interface, unable to load.\n");
+		goto error_no_interface;
+	}
+
+	/* Now that we have a known interface, initialize it */
+	if (interface->init)
+		interface->init(interface);
+
+	/*
+	 * Register the driver
+	 *
+	 * TODO: Can we use the bus detection code to check for the interface
+	 *       or all or part of the method ID path?
+	 */
+	status = acpi_bus_register_driver(&acer_acpi_driver);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_ERR ACER_LOGPREFIX "Unable to register driver, \
+		aborting.\n");
+		goto error_acpi_bus_register;
+	}
+
+	/* Check if this laptop requires the keyboard quirk */
+	if (keyboard_quirk()) {
+		set_keyboard_quirk();
+		printk(KERN_INFO ACER_LOGPREFIX "Setting keyboard quirk to \
+		enable multimedia keys\n");
+	}
+
+	/*
+	 * Finally, override any initial settings with values from the
+	 * command line
+	 */
+	acpi_commandline_init();
+
+	return status;
+
+error_acpi_bus_register:
+error_no_interface:
+	return -ENODEV;
+}
+
+static void __exit acer_acpi_exit(void)
+{
+	acpi_bus_unregister_driver(&acer_acpi_driver);
+
+	if (interface->free)
+		interface->free(interface);
+
+	printk(KERN_INFO ACER_LOGPREFIX "Acer Laptop ACPI Extras unloaded\n");
+	return;
+}
+
+module_init(acer_acpi_init);
+module_exit(acer_acpi_exit);
-- 
1.4.2.1


  reply	other threads:[~2007-07-17 14:45 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-07-17 14:44 [RFC][PATCH 0/5] ACPI: acer_acpi Carlos Corbacho
2007-07-17 14:45 ` Carlos Corbacho [this message]
2007-07-19  0:58 ` Len Brown
2007-07-19  1:04   ` Carlos Corbacho
2007-07-25 10:12   ` Thomas Renninger
2007-08-07 17:58     ` Carlos Corbacho

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=200707171545.54037.cathectic@gmail.com \
    --to=cathectic@gmail.com \
    --cc=linux-acpi@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.