patch-2.4.0-prerelease linux/drivers/acpi/ec.c

Next file: linux/drivers/acpi/ec.h
Previous file: linux/drivers/acpi/driver.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test12/linux/drivers/acpi/ec.c linux/drivers/acpi/ec.c
@@ -1,37 +1,34 @@
 /*
- *	ec.c - Embedded controller support
+ *  ec.c - Embedded controller support
  *
- *	Copyright (C) 2000 Andrew Henroid
+ *  Copyright (C) 2000 Andrew Henroid
  *
- *	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
+ *  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/types.h>
-#include <linux/slab.h>
-#include <linux/pm.h>
 #include <linux/acpi.h>
-#include <linux/delay.h>
-#include <asm/io.h>
+#include <linux/slab.h>
 #include "acpi.h"
 #include "driver.h"
+#include "ec.h"
 
 #define _COMPONENT	OS_DEPENDENT
 	MODULE_NAME	("ec")
 
-#define ACPI_EC_HID	"PNP0A09"
+#define ACPI_EC_HID	"PNP0C09"
 
 enum
 {
@@ -52,88 +49,478 @@
 	ACPI_EC_QUERY = 0x84,
 };
 
+typedef struct
+{
+	ACPI_HANDLE		acpi_handle;
+	u32			gpe_bit;
+	ACPI_IO_ADDRESS 	status_port;
+	ACPI_IO_ADDRESS 	data_port;
+	u32			need_global_lock;
+} ec_context_t;
+
+
+typedef struct 
+{
+    ec_context_t	*ec;
+    u8			data;
+
+} EC_QUERY_DATA;
+
+static char object_name[] = {'_', 'Q', '0', '0', '\0'};
+
+static char hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+
+
+static ACPI_STATUS
+ec_io_wait (
+    ec_context_t              *ec,
+    EC_EVENT                wait_event)
+{
+    EC_STATUS               ec_status = 0;
+    UINT32                  i = 100;
+
+    if (!ec || ((wait_event != EC_EVENT_OUTPUT_BUFFER_FULL) 
+        && (wait_event != EC_EVENT_INPUT_BUFFER_EMPTY)))
+        return(AE_BAD_PARAMETER);
+
+    /* 
+     * Wait for Event:
+     * ---------------
+     * Poll the EC status register waiting for the event to occur.
+     * Note that we'll wait a maximum of 1ms in 10us chunks.
+     */
+    switch (wait_event) {
+    case EC_EVENT_OUTPUT_BUFFER_FULL:
+        do {
+            ec_status = acpi_os_in8(ec->status_port);
+            if (ec_status & EC_FLAG_OUTPUT_BUFFER)
+                return(AE_OK);
+            acpi_os_sleep_usec(10);
+        } while (--i>0);
+        break;
+    case EC_EVENT_INPUT_BUFFER_EMPTY:
+        do {
+            ec_status = acpi_os_in8(ec->status_port);
+            if (!(ec_status & EC_FLAG_INPUT_BUFFER))
+                return(AE_OK);
+            acpi_os_sleep_usec(10);
+        } while (--i>0);
+        break;
+    }
+
+    return(AE_TIME);
+}
+
+static ACPI_STATUS
+ec_io_read (
+    ec_context_t              *ec,
+    ACPI_IO_ADDRESS         io_port,
+    UINT8                   *data,
+    EC_EVENT                wait_event)
+{
+    ACPI_STATUS             status = AE_OK;
+
+    if (!ec || !data)
+        return(AE_BAD_PARAMETER);
+
+    *data = acpi_os_in8(io_port);
+
+    if (wait_event)
+        status = ec_io_wait(ec, wait_event);
+
+    return(status);
+}
+
+static ACPI_STATUS
+ec_io_write (
+    ec_context_t              *ec,
+    ACPI_IO_ADDRESS         io_port,
+    UINT8                   data,
+    EC_EVENT                wait_event)
+{
+    ACPI_STATUS             status = AE_OK;
+
+    if (!ec)
+        return(AE_BAD_PARAMETER);
+
+    acpi_os_out8(io_port, data);
+
+    if (wait_event)
+        status = ec_io_wait(ec, wait_event);
+
+    return(status);
+}
+
+static ACPI_STATUS
+ec_read (
+    ec_context_t              *ec,
+    UINT8                   address,
+    UINT8                   *data)
+{
+    ACPI_STATUS             status = AE_OK;
+
+    FUNCTION_TRACE("ec_read");
+
+    if (!ec || !data)
+        return_ACPI_STATUS(AE_BAD_PARAMETER);
+
+    status = ec_io_write(ec, ec->status_port, EC_COMMAND_READ, EC_EVENT_INPUT_BUFFER_EMPTY);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'read command' to EC.\n"));
+        return_ACPI_STATUS(status);
+    }
+
+    status = ec_io_write(ec, ec->data_port, address, EC_EVENT_OUTPUT_BUFFER_FULL);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'read address' to EC.\n"));
+        return_ACPI_STATUS(status);
+    }
+
+    status = ec_io_read(ec, ec->data_port, data, EC_EVENT_NONE);
+
+    DEBUG_PRINT(ACPI_INFO, ("Read data[0x%02x] from address[0x%02x] on ec.\n", (*data), address));
+
+    return_ACPI_STATUS(status);
+}
+
+static ACPI_STATUS
+ec_write (
+    ec_context_t              *ec,
+    UINT8                   address,
+    UINT8                   data)
+{
+    ACPI_STATUS             status = AE_OK;
+
+    FUNCTION_TRACE("ec_write");
+
+    if (!ec)
+        return_ACPI_STATUS(AE_BAD_PARAMETER);
+
+    status = ec_io_write(ec, ec->status_port, EC_COMMAND_WRITE, EC_EVENT_INPUT_BUFFER_EMPTY);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'write command' to EC.\n"));
+        return_ACPI_STATUS(status);
+    }
+
+    status = ec_io_write(ec, ec->data_port, address, EC_EVENT_INPUT_BUFFER_EMPTY);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'write address' to EC.\n"));
+        return_ACPI_STATUS(status);
+    }
+
+    status = ec_io_write(ec, ec->data_port, data, EC_EVENT_INPUT_BUFFER_EMPTY);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'write data' to EC.\n"));
+        return_ACPI_STATUS(status);
+    }
+
+    DEBUG_PRINT(ACPI_INFO, ("Wrote data[0x%02x] to address[0x%02x] on ec.\n", data, address));
+
+    return_ACPI_STATUS(status);
+}
+
+static ACPI_STATUS
+ec_transaction (
+    ec_context_t              *ec,
+    EC_REQUEST              *request)
+{
+    ACPI_STATUS             status = AE_OK;
+
+    FUNCTION_TRACE("ec_transaction");
+
+    if (!ec || !request)
+        return_ACPI_STATUS(AE_BAD_PARAMETER);
+
+    /*
+     * Obtaining semaphore (mutex) to serialize all EC transactions.
+     */
+    /*
+    DEBUG_PRINT(ACPI_INFO, ("Calling acpi_os_wait_semaphore(%p, 1, %d)\n", ec->mutex, EC_DEFAULT_TIMEOUT));
+    status = acpi_os_wait_semaphore(ec->mutex, 1, EC_DEFAULT_TIMEOUT);
+    if (ACPI_FAILURE(status))
+        return_ACPI_STATUS(status);
+    */
+
+    /*
+     * Perform the transaction.
+     */
+    switch (request->command) {
+
+    case EC_COMMAND_READ:
+        status = ec_read(ec, request->address, &(request->data));
+        break;
+
+    case EC_COMMAND_WRITE:
+        status = ec_write(ec, request->address, request->data);
+        break;
+
+    default:
+        status = AE_SUPPORT;
+        break;
+    }
+
+    /*
+     * Signal the semaphore (mutex) to indicate transaction completion.
+     */
+    /*
+    DEBUG_PRINT(ACPI_INFO, ("Calling acpi_os_signal_semaphore(%p, 1)\n", ec->mutex));
+    acpi_os_signal_semaphore(ec->mutex, 1);
+    */
+
+    return_ACPI_STATUS(status);
+}
+
+static ACPI_STATUS 
+ec_space_setup (
+    ACPI_HANDLE                 region_handle,
+    UINT32                      function,
+    void                        *handler_context,
+    void                        **return_context)
+{
+	// TODO: What is this function for?
+	/* 
+	 * The ec object is in the handler context and is needed
+	 * when calling the ec_space_handler.
+	 */
+	*return_context = handler_context;
+
+    return AE_OK;
+}
+
+
 
-static int acpi_ec_data = 0;
-static int acpi_ec_status = 0;
-static DECLARE_WAIT_QUEUE_HEAD(acpi_ec_wait);
 
-/*
- * handle GPE
- */
 static void
-acpi_ec_gpe(void *context)
+ec_query_handler (
+    void                    *context)
 {
-	printk(KERN_INFO "ACPI: EC GPE\n");
-	if (waitqueue_active(&acpi_ec_wait))
-		wake_up_interruptible(&acpi_ec_wait);
+    ACPI_STATUS             status = AE_OK;
+    EC_QUERY_DATA           *ec_q = (EC_QUERY_DATA*)context;
+
+    FUNCTION_TRACE("ec_query_handler");
+
+    if (!ec_q || !ec_q->ec) {
+        DEBUG_PRINT(ACPI_ERROR, ("Invalid (NULL) context.\n"));
+        return_VOID;
+    }
+
+    /*
+     * Evaluate _Qxx:
+     * --------------
+     * Evaluate corresponding _Qxx method.  Note that a zero query
+     * value indicates a spurious EC_SCI (no such thing as _Q00).
+     */
+    object_name[2] = hex[((ec_q->data >> 4) & 0x0F)];
+    object_name[3] = hex[(ec_q->data & 0x0F)];
+
+    DEBUG_PRINT(ACPI_INFO, ("Read query data[0x%02x] from ec - evaluating [%s].\n", ec_q->data, object_name));
+
+    status = acpi_evaluate_object(ec_q->ec->acpi_handle, object_name, NULL, NULL);
+
+    kfree(ec_q);
+
+    return_VOID;
 }
 
 /*
- * wait for read/write status to clear
+ * handle GPE
  */
 static void
-acpi_ec_wait_control(void)
+ec_gpe_handler(void *context)
 {
-		udelay(1);
-		while(inb(acpi_ec_status) & ACPI_EC_IBF)
-				udelay(10);
+    ACPI_STATUS             status = AE_OK;
+    ec_context_t            *ec = (ec_context_t *) context;
+    EC_QUERY_DATA           *ec_q = NULL;
+    EC_STATUS               ec_status = 0;
+
+    FUNCTION_TRACE("ec_gpe_handler");
+
+    if (!ec) {
+        DEBUG_PRINT(ACPI_INFO, ("Invalid (NULL) context.\n"));
+        return_VOID;
+    }
+
+    // GET SPINLOCK!
+
+    /*
+     * EC_SCI?
+     * -------
+     * Check the EC_SCI bit to see if this is an EC_SCI event.  If not (e.g.
+     * OBF/IBE) just return, as we already poll to detect these events.
+     */
+    ec_status = acpi_os_in8(ec->status_port);
+    DEBUG_PRINT(ACPI_INFO, ("EC Status Register: [0x%02x]\n", ec_status));
+    if (!(ec_status & EC_FLAG_SCI))
+        return_VOID;
+
+    DEBUG_PRINT(ACPI_INFO, ("EC_SCI detected - running QUERY.\n"));
+
+    // TODO: Need GFP_ATOMIC 'switch' for OSL interface...
+    ec_q = kmalloc(sizeof(EC_QUERY_DATA), GFP_ATOMIC);
+    if (!ec_q) {
+        DEBUG_PRINT(ACPI_INFO, ("Memory allocation failure.\n"));
+        return_VOID;
+    }
+
+    ec_q->ec = ec;
+    ec_q->data = 0;
+
+    /*
+     * Run Query:
+     * ----------
+     * Query the EC to find out which _Qxx method we need to evaluate.
+     * Note that successful completion of the query causes the EC_SCI
+     * bit to be cleared (and thus clearing the interrupt source).
+     */
+    status = ec_io_write(ec, ec->status_port, EC_COMMAND_QUERY, EC_EVENT_OUTPUT_BUFFER_FULL);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Unable to send 'query command' to EC.\n"));
+        goto End;
+    }
+
+    status = ec_io_read(ec, ec->data_port, &(ec_q->data), EC_EVENT_NONE);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_WARN, ("Error reading query data.\n"));
+        goto End;
+    }
+
+    // RELEASE SPINLOCK!
+
+    if (!ec_q->data) {
+        DEBUG_PRINT(ACPI_WARN, ("Spurious EC SCI detected.\n"));
+        status = AE_ERROR;
+        goto End;
+    }
+
+    /*
+     * Defer _Qxx Execution:
+     * ---------------------
+     * Can't evaluate this method now 'cause we're at interrupt-level.
+     */
+    status = acpi_os_queue_for_execution(OSD_PRIORITY_GPE, ec_query_handler, ec_q);
+    if (ACPI_FAILURE(status)) {
+        DEBUG_PRINT(ACPI_ERROR, ("Unable to defer _Qxx method evaluation.\n"));
+        goto End;
+    }
+
+End:
+    if (ACPI_FAILURE(status))
+        kfree(ec_q);
+    
+    return_VOID;
 }
 
-/*
- * read a byte from the EC
- */
-int
-acpi_ec_read(int addr, int *value)
+static ACPI_STATUS
+ec_region_setup (
+    ACPI_HANDLE handle,
+    u32 function,
+    void *handler_context,
+    void **region_context)
 {
-	if (!acpi_ec_data || !acpi_ec_status)
-		return -1;
+	FUNCTION_TRACE("acpi_ec_region_setup");
 
-		outb(ACPI_EC_READ, acpi_ec_status);
-		acpi_ec_wait_control();
-		outb(addr, acpi_ec_data);
-		acpi_ec_wait_control();
-		interruptible_sleep_on(&acpi_ec_wait);
-		*value = inb(acpi_ec_data);
+	printk("acpi_ec_region_setup\n");
 
-	return 0;
+	if (function == ACPI_REGION_DEACTIVATE)
+	{
+		if (*region_context)
+		{
+			acpi_cm_free (*region_context);
+			*region_context = NULL;
+		}
+
+		return_ACPI_STATUS (AE_OK);
+	}
+
+	*region_context = NULL;
+
+	return_ACPI_STATUS (AE_OK);
 }
 
-/*
- * write a byte to the EC
- */
-int
-acpi_ec_write(int addr, int value)
-{
-	if (!acpi_ec_data || !acpi_ec_status)
-		return -1;
+/*****************************************************************************
+ * 
+ * FUNCTION:    ec_region_handler
+ *
+ * PARAMETERS:  function            - Read or Write operation
+ *              address             - Where in the space to read or write
+ *              bit_width            - Field width in bits (8, 16, or 32)
+ *              value               - Pointer to in or out value
+ *              context             - context pointer
+ *
+ * RETURN:      <TBD>
+ *
+ * DESCRIPTION: Handler for the Embedded Controller (EC) address space 
+ *              (Op Region)
+ *
+ ****************************************************************************/
 
-		outb(ACPI_EC_WRITE, acpi_ec_status);
-		acpi_ec_wait_control();
-		outb(addr, acpi_ec_data);
-		acpi_ec_wait_control();
-		outb(value, acpi_ec_data);
-		acpi_ec_wait_control();
-		interruptible_sleep_on(&acpi_ec_wait);
+static ACPI_STATUS
+ec_region_handler (
+    UINT32                  function,
+    ACPI_PHYSICAL_ADDRESS   address,
+    UINT32                  bit_width,
+    UINT32                  *value,
+    void                    *handler_context,
+    void                    *region_context)
+{
+    ACPI_STATUS             status = AE_OK;
+    ec_context_t              *ec = NULL;
+    EC_REQUEST              ec_request;
+
+    FUNCTION_TRACE("ec_space_handler");
+
+    if (address > 0xFF || bit_width != 8 || !value || !handler_context)
+        return_ACPI_STATUS(AE_BAD_PARAMETER);
+
+    ec = (ec_context_t*)handler_context;
+
+    switch (function) {
+
+    case ADDRESS_SPACE_READ:
+        ec_request.command = EC_COMMAND_READ;
+        ec_request.address = address;
+        ec_request.data = 0;
+        break;
+
+    case ADDRESS_SPACE_WRITE:
+        ec_request.command = EC_COMMAND_WRITE;
+        ec_request.address = address;
+        ec_request.data = (UINT8)(*value);
+        break;
+
+    default:
+        DEBUG_PRINT(ACPI_WARN, ("Received request with invalid function [0x%08X].\n", function));
+        return_ACPI_STATUS(AE_BAD_PARAMETER);
+        break;
+    }
+
+    DEBUG_PRINT(ACPI_INFO, ("device[ec] command[0x%02X] address[0x%02X] data[0x%02X]\n", ec_request.command, ec_request.address, ec_request.data));
+
+    /*
+     * Perform the Transaction.
+     */
+    status = ec_transaction(ec, &ec_request);
+    if (ACPI_SUCCESS(status))
+        (*value) = (UINT32)ec_request.data;
 
-	return 0;
+    return_ACPI_STATUS(status);
 }
 
 /*
  * Get Embedded Controller information
  */
 static ACPI_STATUS
-acpi_find_ec(ACPI_HANDLE handle, u32 level, void *ctx, void **value)
+found_ec(
+	ACPI_HANDLE handle,
+	u32 level, 
+	void *ctx, 
+	void **value)
 {
-	ACPI_DEVICE_INFO dev_info;
+	ACPI_STATUS status;
 	ACPI_OBJECT obj;
 	ACPI_BUFFER buf;
 	RESOURCE *res;
-	int gpe;
-
-	if (!ACPI_SUCCESS(acpi_get_object_info(handle, &dev_info))
-		|| !(dev_info.valid & ACPI_VALID_HID)
-		|| 0 != STRCMP(dev_info.hardware_id, ACPI_EC_HID))
-		return AE_OK;
+	ec_context_t *ec_cxt;
 
 	buf.length = 0;
 	buf.pointer = NULL;
@@ -142,40 +529,73 @@
 
 	buf.pointer = kmalloc(buf.length, GFP_KERNEL);
 	if (!buf.pointer)
-		return AE_OK;
+		return AE_NO_MEMORY;
 
 	if (!ACPI_SUCCESS(acpi_get_current_resources(handle, &buf))) {
 		kfree(buf.pointer);
 		return AE_OK;
 	}
 
+	ec_cxt = kmalloc(sizeof(ec_context_t), GFP_KERNEL);
+	if (!ec_cxt) {
+		kfree(buf.pointer);
+		return AE_NO_MEMORY;
+	}
+
+	ec_cxt->acpi_handle = handle;
+
 	res = (RESOURCE*) buf.pointer;
-	acpi_ec_data = (int) res->data.io.min_base_address;
-	res = (RESOURCE*)((u8*) buf.pointer + res->length);
-	acpi_ec_status = (int) res->data.io.min_base_address;
+	ec_cxt->data_port = res->data.io.min_base_address;
+	res = NEXT_RESOURCE(res);
+	ec_cxt->status_port = (int) res->data.io.min_base_address;
 
 	kfree(buf.pointer);
 
+	/* determine GPE bit */
+	/* BUG: in acpi 2.0 this could return a package */
 	buf.length = sizeof(obj);
 	buf.pointer = &obj;
 	if (!ACPI_SUCCESS(acpi_evaluate_object(handle, "_GPE", NULL, &buf))
 		|| obj.type != ACPI_TYPE_NUMBER)
 		return AE_OK;
-	gpe = (int) obj.number.value;
 
-	printk(KERN_INFO "ACPI: found EC @ (0x%02x,0x%02x,%d)\n",
-		   acpi_ec_data, acpi_ec_status, gpe);
+	ec_cxt->gpe_bit = obj.number.value;
+
+	/* determine if we need the Global Lock when accessing */
+	buf.length = sizeof(obj);
+	buf.pointer = &obj;
+
+	status = acpi_evaluate_object(handle, "_GLK", NULL, &buf);
+	if (status == AE_NOT_FOUND)
+		ec_cxt->need_global_lock = 0;
+	else if (!ACPI_SUCCESS(status) || obj.type != ACPI_TYPE_NUMBER) {
+		DEBUG_PRINT(ACPI_ERROR, ("_GLK failed\n"));
+		return AE_OK;
+	}
+
+	ec_cxt->need_global_lock = obj.number.value;
+
+	printk(KERN_INFO "ACPI: found EC @ (0x%02x,0x%02x,gpe %d GL %d)\n",
+		ec_cxt->data_port, ec_cxt->status_port, ec_cxt->gpe_bit,
+		ec_cxt->need_global_lock);
 
 	if (!ACPI_SUCCESS(acpi_install_gpe_handler(
-		gpe,
-		(ACPI_EVENT_LEVEL_TRIGGERED
-		 | ACPI_EVENT_EDGE_TRIGGERED),
-		acpi_ec_gpe,
-		NULL))) {
+		ec_cxt->gpe_bit,
+		ACPI_EVENT_EDGE_TRIGGERED,
+		ec_gpe_handler,
+		ec_cxt))) {
 		
-		DEBUG_PRINT(ACPI_ERROR, ("Could not install GPE handler for EC.\n"));
+		REPORT_ERROR(("Could not install GPE handler for EC.\n"));
 		return AE_OK;
 	}
+
+	status = acpi_install_address_space_handler (handle, ADDRESS_SPACE_EC, 
+		    ec_region_handler, ec_region_setup, ec_cxt);
+
+	if (!ACPI_SUCCESS(status)) {
+		REPORT_ERROR(("Could not install EC address "
+			"space handler, error %s\n", acpi_cm_format_exception (status)));
+	}
 	
 	return AE_OK;
 }
@@ -183,11 +603,19 @@
 int
 acpi_ec_init(void)
 {
-	acpi_walk_namespace(ACPI_TYPE_DEVICE,
-				ACPI_ROOT_OBJECT,
-				ACPI_UINT32_MAX,
-				acpi_find_ec,
-				NULL,
-				NULL);
+	acpi_get_devices(ACPI_EC_HID, 
+			found_ec,
+			NULL,
+			NULL);
+
+	return 0;
+}
+
+int
+acpi_ec_terminate(void)
+{
+	/* TODO */	
+	/* walk list of EC's */
+	/* free their context and release resources */
 	return 0;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)