/* * ec.c - Embedded controller support * * 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 */ #include #include #include #include "acpi.h" #include "driver.h" #include "ec.h" #define _COMPONENT OS_DEPENDENT MODULE_NAME ("ec") #define ACPI_EC_HID "PNP0C09" enum { ACPI_EC_SMI = 0x40, ACPI_EC_SCI = 0x20, ACPI_EC_BURST = 0x10, ACPI_EC_CMD = 0x08, ACPI_EC_IBF = 0x02, ACPI_EC_OBF = 0x01 }; enum { ACPI_EC_READ = 0x80, ACPI_EC_WRITE = 0x81, ACPI_EC_BURST_ENABLE = 0x82, ACPI_EC_BURST_DISABLE = 0x83, 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 void ec_query_handler ( void *context) { 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; } /* * handle GPE */ static void ec_gpe_handler(void *context) { 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; } static ACPI_STATUS ec_region_setup ( ACPI_HANDLE handle, u32 function, void *handler_context, void **region_context) { FUNCTION_TRACE("acpi_ec_region_setup"); 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); } /***************************************************************************** * * 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: * * DESCRIPTION: Handler for the Embedded Controller (EC) address space * (Op Region) * ****************************************************************************/ 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_ACPI_STATUS(status); } /* * Get Embedded Controller information */ static ACPI_STATUS found_ec( ACPI_HANDLE handle, u32 level, void *ctx, void **value) { ACPI_STATUS status; ACPI_OBJECT obj; ACPI_BUFFER buf; RESOURCE *res; ec_context_t *ec_cxt; buf.length = 0; buf.pointer = NULL; if (acpi_get_current_resources(handle, &buf) != AE_BUFFER_OVERFLOW) return AE_OK; buf.pointer = kmalloc(buf.length, GFP_KERNEL); if (!buf.pointer) 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; 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_INTEGER) return AE_OK; ec_cxt->gpe_bit = obj.integer.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_INTEGER) { DEBUG_PRINT(ACPI_ERROR, ("_GLK failed\n")); return AE_OK; } ec_cxt->need_global_lock = obj.integer.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( ec_cxt->gpe_bit, ACPI_EVENT_EDGE_TRIGGERED, ec_gpe_handler, ec_cxt))) { 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; } int acpi_ec_init(void) { 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; }