/*
* acpi_ec.c - ACPI Embedded Controller Driver ($Revision: 38 $)
*
+ * Copyright (C) 2004 Luming Yu <luming.yu@intel.com>
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
*
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/interrupt.h>
#include <asm/io.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
#include <acpi/actypes.h>
#define _COMPONENT ACPI_EC_COMPONENT
-ACPI_MODULE_NAME ("acpi_ec")
-
+ACPI_MODULE_NAME("acpi_ec")
#define ACPI_EC_COMPONENT 0x00100000
#define ACPI_EC_CLASS "embedded_controller"
#define ACPI_EC_HID "PNP0C09"
#define ACPI_EC_DRIVER_NAME "ACPI Embedded Controller Driver"
#define ACPI_EC_DEVICE_NAME "Embedded Controller"
#define ACPI_EC_FILE_INFO "info"
-
-
+#undef PREFIX
+#define PREFIX "ACPI: EC: "
+/* EC status register */
#define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */
#define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */
+#define ACPI_EC_FLAG_BURST 0x10 /* burst mode */
#define ACPI_EC_FLAG_SCI 0x20 /* EC-SCI occurred */
+/* EC commands */
+enum ec_command {
+ ACPI_EC_COMMAND_READ = 0x80,
+ ACPI_EC_COMMAND_WRITE = 0x81,
+ ACPI_EC_BURST_ENABLE = 0x82,
+ ACPI_EC_BURST_DISABLE = 0x83,
+ ACPI_EC_COMMAND_QUERY = 0x84,
+};
+/* EC events */
+enum ec_event {
+ ACPI_EC_EVENT_OBF_1 = 1, /* Output buffer full */
+ ACPI_EC_EVENT_IBF_0, /* Input buffer empty */
+};
-#define ACPI_EC_EVENT_OBF 0x01 /* Output buffer full */
-#define ACPI_EC_EVENT_IBE 0x02 /* Input buffer empty */
-
-#define ACPI_EC_UDELAY 100 /* Poll @ 100us increments */
-#define ACPI_EC_UDELAY_COUNT 1000 /* Wait 10ms max. during EC ops */
+#define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */
#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
-#define ACPI_EC_COMMAND_READ 0x80
-#define ACPI_EC_COMMAND_WRITE 0x81
-#define ACPI_EC_COMMAND_QUERY 0x84
+static enum ec_mode {
+ EC_INTR = 1, /* Output buffer full */
+ EC_POLL, /* Input buffer empty */
+} acpi_ec_mode = EC_INTR;
-static int acpi_ec_add (struct acpi_device *device);
-static int acpi_ec_remove (struct acpi_device *device, int type);
-static int acpi_ec_start (struct acpi_device *device);
-static int acpi_ec_stop (struct acpi_device *device, int type);
+static int acpi_ec_remove(struct acpi_device *device, int type);
+static int acpi_ec_start(struct acpi_device *device);
+static int acpi_ec_stop(struct acpi_device *device, int type);
+static int acpi_ec_add(struct acpi_device *device);
static struct acpi_driver acpi_ec_driver = {
- .name = ACPI_EC_DRIVER_NAME,
- .class = ACPI_EC_CLASS,
- .ids = ACPI_EC_HID,
- .ops = {
- .add = acpi_ec_add,
- .remove = acpi_ec_remove,
- .start = acpi_ec_start,
- .stop = acpi_ec_stop,
- },
-};
-
-struct acpi_ec {
- acpi_handle handle;
- unsigned long uid;
- unsigned long gpe_bit;
- struct acpi_generic_address status_addr;
- struct acpi_generic_address command_addr;
- struct acpi_generic_address data_addr;
- unsigned long global_lock;
- spinlock_t lock;
+ .name = ACPI_EC_DRIVER_NAME,
+ .class = ACPI_EC_CLASS,
+ .ids = ACPI_EC_HID,
+ .ops = {
+ .add = acpi_ec_add,
+ .remove = acpi_ec_remove,
+ .start = acpi_ec_start,
+ .stop = acpi_ec_stop,
+ },
};
/* If we find an EC via the ECDT, we need to keep a ptr to its context */
-static struct acpi_ec *ec_ecdt;
+static struct acpi_ec {
+ acpi_handle handle;
+ unsigned long uid;
+ unsigned long gpe;
+ unsigned long command_addr;
+ unsigned long data_addr;
+ unsigned long global_lock;
+ struct mutex lock;
+ atomic_t query_pending;
+ atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */
+ wait_queue_head_t wait;
+} *ec_ecdt;
/* External interfaces use first EC only, so remember */
static struct acpi_device *first_ec;
Transaction Management
-------------------------------------------------------------------------- */
-static int
-acpi_ec_wait (
- struct acpi_ec *ec,
- u8 event)
+static inline u8 acpi_ec_read_status(struct acpi_ec *ec)
{
- u32 acpi_ec_status = 0;
- u32 i = ACPI_EC_UDELAY_COUNT;
-
- if (!ec)
- return -EINVAL;
-
- /* Poll the EC status register waiting for the event to occur. */
- switch (event) {
- case ACPI_EC_EVENT_OBF:
- do {
- acpi_hw_low_level_read(8, &acpi_ec_status, &ec->status_addr);
- if (acpi_ec_status & ACPI_EC_FLAG_OBF)
- return 0;
- udelay(ACPI_EC_UDELAY);
- } while (--i>0);
- break;
- case ACPI_EC_EVENT_IBE:
- do {
- acpi_hw_low_level_read(8, &acpi_ec_status, &ec->status_addr);
- if (!(acpi_ec_status & ACPI_EC_FLAG_IBF))
- return 0;
- udelay(ACPI_EC_UDELAY);
- } while (--i>0);
- break;
- default:
- return -EINVAL;
- }
-
- return -ETIME;
+ return inb(ec->command_addr);
}
+static inline u8 acpi_ec_read_data(struct acpi_ec *ec)
+{
+ return inb(ec->data_addr);
+}
-static int
-acpi_ec_read (
- struct acpi_ec *ec,
- u8 address,
- u32 *data)
+static inline void acpi_ec_write_cmd(struct acpi_ec *ec, u8 command)
{
- acpi_status status = AE_OK;
- int result = 0;
- unsigned long flags = 0;
- u32 glk = 0;
+ outb(command, ec->command_addr);
+}
- ACPI_FUNCTION_TRACE("acpi_ec_read");
+static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data)
+{
+ outb(data, ec->data_addr);
+}
- if (!ec || !data)
- return_VALUE(-EINVAL);
+static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event)
+{
+ u8 status = acpi_ec_read_status(ec);
+
+ if (event == ACPI_EC_EVENT_OBF_1) {
+ if (status & ACPI_EC_FLAG_OBF)
+ return 1;
+ } else if (event == ACPI_EC_EVENT_IBF_0) {
+ if (!(status & ACPI_EC_FLAG_IBF))
+ return 1;
+ }
- *data = 0;
+ return 0;
+}
- if (ec->global_lock) {
- status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
- if (ACPI_FAILURE(status))
- return_VALUE(-ENODEV);
+static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event)
+{
+ if (acpi_ec_mode == EC_POLL) {
+ unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY);
+ while (time_before(jiffies, delay)) {
+ if (acpi_ec_check_status(ec, event))
+ return 0;
+ }
+ } else {
+ if (wait_event_timeout(ec->wait,
+ acpi_ec_check_status(ec, event),
+ msecs_to_jiffies(ACPI_EC_DELAY)) ||
+ acpi_ec_check_status(ec, event)) {
+ return 0;
+ } else {
+ printk(KERN_ERR PREFIX "acpi_ec_wait timeout,"
+ " status = %d, expect_event = %d\n",
+ acpi_ec_read_status(ec), event);
+ }
}
-
- spin_lock_irqsave(&ec->lock, flags);
- acpi_hw_low_level_write(8, ACPI_EC_COMMAND_READ, &ec->command_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
- if (result)
- goto end;
+ return -ETIME;
+}
- acpi_hw_low_level_write(8, address, &ec->data_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
- if (result)
- goto end;
+#ifdef ACPI_FUTURE_USAGE
+/*
+ * Note: samsung nv5000 doesn't work with ec burst mode.
+ * http://bugzilla.kernel.org/show_bug.cgi?id=4980
+ */
+int acpi_ec_enter_burst_mode(struct acpi_ec *ec)
+{
+ u8 tmp = 0;
+ u8 status = 0;
+
+ status = acpi_ec_read_status(ec);
+ if (status != -EINVAL && !(status & ACPI_EC_FLAG_BURST)) {
+ status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ if (status)
+ goto end;
+ acpi_ec_write_cmd(ec, ACPI_EC_BURST_ENABLE);
+ status = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1);
+ tmp = acpi_ec_read_data(ec);
+ if (tmp != 0x90) { /* Burst ACK byte */
+ return -EINVAL;
+ }
+ }
+ atomic_set(&ec->leaving_burst, 0);
+ return 0;
+ end:
+ ACPI_EXCEPTION((AE_INFO, status, "EC wait, burst mode"));
+ return -1;
+}
- acpi_hw_low_level_read(8, data, &ec->data_addr);
+int acpi_ec_leave_burst_mode(struct acpi_ec *ec)
+{
+ u8 status = 0;
+
+ status = acpi_ec_read_status(ec);
+ if (status != -EINVAL && (status & ACPI_EC_FLAG_BURST)) {
+ status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ if (status)
+ goto end;
+ acpi_ec_write_cmd(ec, ACPI_EC_BURST_DISABLE);
+ acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ }
+ atomic_set(&ec->leaving_burst, 1);
+ return 0;
+ end:
+ ACPI_EXCEPTION((AE_INFO, status, "EC leave burst mode"));
+ return -1;
+}
+#endif /* ACPI_FUTURE_USAGE */
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Read [%02x] from address [%02x]\n",
- *data, address));
+static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command,
+ const u8 * wdata, unsigned wdata_len,
+ u8 * rdata, unsigned rdata_len)
+{
+ int result = 0;
+
+ acpi_ec_write_cmd(ec, command);
+
+ for (; wdata_len > 0; --wdata_len) {
+ result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ if (result) {
+ printk(KERN_ERR PREFIX
+ "write_cmd timeout, command = %d\n", command);
+ goto end;
+ }
+ acpi_ec_write_data(ec, *(wdata++));
+ }
-end:
- spin_unlock_irqrestore(&ec->lock, flags);
+ if (!rdata_len) {
+ result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ if (result) {
+ printk(KERN_ERR PREFIX
+ "finish-write timeout, command = %d\n", command);
+ goto end;
+ }
+ } else if (command == ACPI_EC_COMMAND_QUERY) {
+ atomic_set(&ec->query_pending, 0);
+ }
- if (ec->global_lock)
- acpi_release_global_lock(glk);
+ for (; rdata_len > 0; --rdata_len) {
+ result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1);
+ if (result) {
+ printk(KERN_ERR PREFIX "read timeout, command = %d\n",
+ command);
+ goto end;
+ }
- return_VALUE(result);
+ *(rdata++) = acpi_ec_read_data(ec);
+ }
+ end:
+ return result;
}
-
-static int
-acpi_ec_write (
- struct acpi_ec *ec,
- u8 address,
- u8 data)
+static int acpi_ec_transaction(struct acpi_ec *ec, u8 command,
+ const u8 * wdata, unsigned wdata_len,
+ u8 * rdata, unsigned rdata_len)
{
- int result = 0;
- acpi_status status = AE_OK;
- unsigned long flags = 0;
- u32 glk = 0;
+ int status;
+ u32 glk;
- ACPI_FUNCTION_TRACE("acpi_ec_write");
+ if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata))
+ return -EINVAL;
- if (!ec)
- return_VALUE(-EINVAL);
+ if (rdata)
+ memset(rdata, 0, rdata_len);
+ mutex_lock(&ec->lock);
if (ec->global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
- return_VALUE(-ENODEV);
+ return -ENODEV;
}
- spin_lock_irqsave(&ec->lock, flags);
-
- acpi_hw_low_level_write(8, ACPI_EC_COMMAND_WRITE, &ec->command_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
- if (result)
- goto end;
+ /* Make sure GPE is enabled before doing transaction */
+ acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
- acpi_hw_low_level_write(8, address, &ec->data_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
- if (result)
- goto end;
-
- acpi_hw_low_level_write(8, data, &ec->data_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
- if (result)
+ status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0);
+ if (status) {
+ printk(KERN_DEBUG PREFIX
+ "input buffer is not empty, aborting transaction\n");
goto end;
+ }
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Wrote [%02x] to address [%02x]\n",
- data, address));
+ status = acpi_ec_transaction_unlocked(ec, command,
+ wdata, wdata_len,
+ rdata, rdata_len);
-end:
- spin_unlock_irqrestore(&ec->lock, flags);
+ end:
if (ec->global_lock)
acpi_release_global_lock(glk);
+ mutex_unlock(&ec->lock);
- return_VALUE(result);
+ return status;
+}
+
+static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data)
+{
+ int result;
+ u8 d;
+
+ result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_READ,
+ &address, 1, &d, 1);
+ *data = d;
+ return result;
+}
+
+static int acpi_ec_write(struct acpi_ec *ec, u8 address, u8 data)
+{
+ u8 wdata[2] = { address, data };
+ return acpi_ec_transaction(ec, ACPI_EC_COMMAND_WRITE,
+ wdata, 2, NULL, 0);
}
/*
* Externally callable EC access functions. For now, assume 1 EC only
*/
-int
-ec_read(u8 addr, u8 *val)
+int ec_read(u8 addr, u8 * val)
{
struct acpi_ec *ec;
int err;
- u32 temp_data;
+ u8 temp_data;
if (!first_ec)
return -ENODEV;
if (!err) {
*val = temp_data;
return 0;
- }
- else
+ } else
return err;
}
-int
-ec_write(u8 addr, u8 val)
+EXPORT_SYMBOL(ec_read);
+
+int ec_write(u8 addr, u8 val)
{
struct acpi_ec *ec;
int err;
return err;
}
+EXPORT_SYMBOL(ec_write);
-static int
-acpi_ec_query (
- struct acpi_ec *ec,
- u32 *data)
+int ec_transaction(u8 command,
+ const u8 * wdata, unsigned wdata_len,
+ u8 * rdata, unsigned rdata_len)
{
- int result = 0;
- acpi_status status = AE_OK;
- unsigned long flags = 0;
- u32 glk = 0;
+ struct acpi_ec *ec;
- ACPI_FUNCTION_TRACE("acpi_ec_query");
+ if (!first_ec)
+ return -ENODEV;
- if (!ec || !data)
- return_VALUE(-EINVAL);
+ ec = acpi_driver_data(first_ec);
- *data = 0;
+ return acpi_ec_transaction(ec, command, wdata,
+ wdata_len, rdata, rdata_len);
+}
- if (ec->global_lock) {
- status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
- if (ACPI_FAILURE(status))
- return_VALUE(-ENODEV);
- }
+EXPORT_SYMBOL(ec_transaction);
+
+static int acpi_ec_query(struct acpi_ec *ec, u8 * data)
+{
+ int result;
+ u8 d;
+
+ if (!ec || !data)
+ return -EINVAL;
/*
* Query the EC to find out which _Qxx method we need to evaluate.
* Note that successful completion of the query causes the ACPI_EC_SCI
* bit to be cleared (and thus clearing the interrupt source).
*/
- spin_lock_irqsave(&ec->lock, flags);
- acpi_hw_low_level_write(8, ACPI_EC_COMMAND_QUERY, &ec->command_addr);
- result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
+ result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_QUERY, NULL, 0, &d, 1);
if (result)
- goto end;
-
- acpi_hw_low_level_read(8, data, &ec->data_addr);
- if (!*data)
- result = -ENODATA;
+ return result;
-end:
- spin_unlock_irqrestore(&ec->lock, flags);
-
- if (ec->global_lock)
- acpi_release_global_lock(glk);
+ if (!d)
+ return -ENODATA;
- return_VALUE(result);
+ *data = d;
+ return 0;
}
-
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
-struct acpi_ec_query_data {
- acpi_handle handle;
- u8 data;
-};
-
-static void
-acpi_ec_gpe_query (
- void *ec_cxt)
+static void acpi_ec_gpe_query(void *ec_cxt)
{
- struct acpi_ec *ec = (struct acpi_ec *) ec_cxt;
- u32 value = 0;
- unsigned long flags = 0;
- static char object_name[5] = {'_','Q','0','0','\0'};
- const char hex[] = {'0','1','2','3','4','5','6','7',
- '8','9','A','B','C','D','E','F'};
-
- ACPI_FUNCTION_TRACE("acpi_ec_gpe_query");
-
- if (!ec_cxt)
- goto end;
-
- spin_lock_irqsave(&ec->lock, flags);
- acpi_hw_low_level_read(8, &value, &ec->command_addr);
- spin_unlock_irqrestore(&ec->lock, flags);
+ struct acpi_ec *ec = (struct acpi_ec *)ec_cxt;
+ u8 value = 0;
+ char object_name[8];
- /* TBD: Implement asynch events!
- * NOTE: All we care about are EC-SCI's. Other EC events are
- * handled via polling (yuck!). This is because some systems
- * treat EC-SCIs as level (versus EDGE!) triggered, preventing
- * a purely interrupt-driven approach (grumble, grumble).
- */
- if (!(value & ACPI_EC_FLAG_SCI))
- goto end;
+ if (!ec || acpi_ec_query(ec, &value))
+ return;
- if (acpi_ec_query(ec, &value))
- goto end;
-
- object_name[2] = hex[((value >> 4) & 0x0F)];
- object_name[3] = hex[(value & 0x0F)];
+ snprintf(object_name, 8, "_Q%2.2X", value);
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s\n", object_name));
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s", object_name));
acpi_evaluate_object(ec->handle, object_name, NULL, NULL);
-
-end:
- acpi_enable_gpe(NULL, ec->gpe_bit, ACPI_NOT_ISR);
}
-static u32
-acpi_ec_gpe_handler (
- void *data)
+static u32 acpi_ec_gpe_handler(void *data)
{
- acpi_status status = AE_OK;
- struct acpi_ec *ec = (struct acpi_ec *) data;
+ acpi_status status = AE_OK;
+ u8 value;
+ struct acpi_ec *ec = (struct acpi_ec *)data;
- if (!ec)
- return ACPI_INTERRUPT_NOT_HANDLED;
-
- acpi_disable_gpe(NULL, ec->gpe_bit, ACPI_ISR);
+ if (acpi_ec_mode == EC_INTR) {
+ wake_up(&ec->wait);
+ }
- status = acpi_os_queue_for_execution(OSD_PRIORITY_GPE,
- acpi_ec_gpe_query, ec);
+ value = acpi_ec_read_status(ec);
+ if ((value & ACPI_EC_FLAG_SCI) && !atomic_read(&ec->query_pending)) {
+ atomic_set(&ec->query_pending, 1);
+ status =
+ acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query,
+ ec);
+ }
- if (status == AE_OK)
- return ACPI_INTERRUPT_HANDLED;
- else
- return ACPI_INTERRUPT_NOT_HANDLED;
+ return status == AE_OK ?
+ ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
}
/* --------------------------------------------------------------------------
-------------------------------------------------------------------------- */
static acpi_status
-acpi_ec_space_setup (
- acpi_handle region_handle,
- u32 function,
- void *handler_context,
- void **return_context)
+acpi_ec_space_setup(acpi_handle region_handle,
+ u32 function, void *handler_context, void **return_context)
{
/*
* The EC object is in the handler context and is needed
* when calling the acpi_ec_space_handler.
*/
- if(function == ACPI_REGION_DEACTIVATE)
- *return_context = NULL;
- else
- *return_context = handler_context;
+ *return_context = (function != ACPI_REGION_DEACTIVATE) ?
+ handler_context : NULL;
return AE_OK;
}
-
static acpi_status
-acpi_ec_space_handler (
- u32 function,
- acpi_physical_address address,
- u32 bit_width,
- acpi_integer *value,
- void *handler_context,
- void *region_context)
+acpi_ec_space_handler(u32 function,
+ acpi_physical_address address,
+ u32 bit_width,
+ acpi_integer * value,
+ void *handler_context, void *region_context)
{
- int result = 0;
- struct acpi_ec *ec = NULL;
- u32 temp = 0;
+ int result = 0;
+ struct acpi_ec *ec = NULL;
+ u64 temp = *value;
+ acpi_integer f_v = 0;
+ int i = 0;
- ACPI_FUNCTION_TRACE("acpi_ec_space_handler");
+ if ((address > 0xFF) || !value || !handler_context)
+ return AE_BAD_PARAMETER;
- if ((address > 0xFF) || (bit_width != 8) || !value || !handler_context)
- return_VALUE(AE_BAD_PARAMETER);
+ if (bit_width != 8 && acpi_strict) {
+ return AE_BAD_PARAMETER;
+ }
- ec = (struct acpi_ec *) handler_context;
+ ec = (struct acpi_ec *)handler_context;
+ next_byte:
switch (function) {
case ACPI_READ:
- result = acpi_ec_read(ec, (u8) address, &temp);
- *value = (acpi_integer) temp;
+ temp = 0;
+ result = acpi_ec_read(ec, (u8) address, (u8 *) & temp);
break;
case ACPI_WRITE:
- result = acpi_ec_write(ec, (u8) address, (u8) *value);
+ result = acpi_ec_write(ec, (u8) address, (u8) temp);
break;
default:
result = -EINVAL;
+ goto out;
break;
}
+ bit_width -= 8;
+ if (bit_width) {
+ if (function == ACPI_READ)
+ f_v |= temp << 8 * i;
+ if (function == ACPI_WRITE)
+ temp >>= 8;
+ i++;
+ address++;
+ goto next_byte;
+ }
+
+ if (function == ACPI_READ) {
+ f_v |= temp << 8 * i;
+ *value = f_v;
+ }
+
+ out:
switch (result) {
case -EINVAL:
- return_VALUE(AE_BAD_PARAMETER);
+ return AE_BAD_PARAMETER;
break;
case -ENODEV:
- return_VALUE(AE_NOT_FOUND);
+ return AE_NOT_FOUND;
break;
case -ETIME:
- return_VALUE(AE_TIME);
+ return AE_TIME;
break;
default:
- return_VALUE(AE_OK);
+ return AE_OK;
}
-
}
-
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
-struct proc_dir_entry *acpi_ec_dir;
-
+static struct proc_dir_entry *acpi_ec_dir;
-static int
-acpi_ec_read_info (
- char *page,
- char **start,
- off_t off,
- int count,
- int *eof,
- void *data)
+static int acpi_ec_read_info(struct seq_file *seq, void *offset)
{
- struct acpi_ec *ec = (struct acpi_ec *) data;
- char *p = page;
- int len = 0;
+ struct acpi_ec *ec = (struct acpi_ec *)seq->private;
- ACPI_FUNCTION_TRACE("acpi_ec_read_info");
-
- if (!ec || (off != 0))
+ if (!ec)
goto end;
- p += sprintf(p, "gpe bit: 0x%02x\n",
- (u32) ec->gpe_bit);
- p += sprintf(p, "ports: 0x%02x, 0x%02x\n",
- (u32) ec->status_addr.address, (u32) ec->data_addr.address);
- p += sprintf(p, "use global lock: %s\n",
- ec->global_lock?"yes":"no");
+ seq_printf(seq, "gpe: 0x%02x\n", (u32) ec->gpe);
+ seq_printf(seq, "ports: 0x%02x, 0x%02x\n",
+ (u32) ec->command_addr, (u32) ec->data_addr);
+ seq_printf(seq, "use global lock: %s\n",
+ ec->global_lock ? "yes" : "no");
+ acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
-end:
- len = (p - page);
- if (len <= off+count) *eof = 1;
- *start = page + off;
- len -= off;
- if (len>count) len = count;
- if (len<0) len = 0;
+ end:
+ return 0;
+}
- return_VALUE(len);
+static int acpi_ec_info_open_fs(struct inode *inode, struct file *file)
+{
+ return single_open(file, acpi_ec_read_info, PDE(inode)->data);
}
+static struct file_operations acpi_ec_info_ops = {
+ .open = acpi_ec_info_open_fs,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
-static int
-acpi_ec_add_fs (
- struct acpi_device *device)
+static int acpi_ec_add_fs(struct acpi_device *device)
{
- struct proc_dir_entry *entry = NULL;
-
- ACPI_FUNCTION_TRACE("acpi_ec_add_fs");
+ struct proc_dir_entry *entry = NULL;
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
- acpi_ec_dir);
+ acpi_ec_dir);
if (!acpi_device_dir(device))
- return_VALUE(-ENODEV);
+ return -ENODEV;
}
- entry = create_proc_read_entry(ACPI_EC_FILE_INFO, S_IRUGO,
- acpi_device_dir(device), acpi_ec_read_info,
- acpi_driver_data(device));
+ entry = create_proc_entry(ACPI_EC_FILE_INFO, S_IRUGO,
+ acpi_device_dir(device));
if (!entry)
- ACPI_DEBUG_PRINT((ACPI_DB_WARN,
- "Unable to create '%s' fs entry\n",
- ACPI_EC_FILE_INFO));
+ return -ENODEV;
+ else {
+ entry->proc_fops = &acpi_ec_info_ops;
+ entry->data = acpi_driver_data(device);
+ entry->owner = THIS_MODULE;
+ }
- return_VALUE(0);
+ return 0;
}
-
-static int
-acpi_ec_remove_fs (
- struct acpi_device *device)
+static int acpi_ec_remove_fs(struct acpi_device *device)
{
- ACPI_FUNCTION_TRACE("acpi_ec_remove_fs");
if (acpi_device_dir(device)) {
remove_proc_entry(ACPI_EC_FILE_INFO, acpi_device_dir(device));
acpi_device_dir(device) = NULL;
}
- return_VALUE(0);
+ return 0;
}
-
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
-static int
-acpi_ec_add (
- struct acpi_device *device)
+static int acpi_ec_add(struct acpi_device *device)
{
- int result = 0;
- acpi_status status = AE_OK;
- struct acpi_ec *ec = NULL;
- unsigned long uid;
-
- ACPI_FUNCTION_TRACE("acpi_ec_add");
+ int result = 0;
+ acpi_status status = AE_OK;
+ struct acpi_ec *ec = NULL;
if (!device)
- return_VALUE(-EINVAL);
+ return -EINVAL;
- ec = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
+ ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL);
if (!ec)
- return_VALUE(-ENOMEM);
- memset(ec, 0, sizeof(struct acpi_ec));
+ return -ENOMEM;
ec->handle = device->handle;
ec->uid = -1;
- ec->lock = SPIN_LOCK_UNLOCKED;
+ mutex_init(&ec->lock);
+ atomic_set(&ec->query_pending, 0);
+ if (acpi_ec_mode == EC_INTR) {
+ atomic_set(&ec->leaving_burst, 1);
+ init_waitqueue_head(&ec->wait);
+ }
strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_EC_CLASS);
acpi_driver_data(device) = ec;
/* Use the global lock for all EC transactions? */
acpi_evaluate_integer(ec->handle, "_GLK", NULL, &ec->global_lock);
- /* If our UID matches the UID for the ECDT-enumerated EC,
- we now have the *real* EC info, so kill the makeshift one.*/
- acpi_evaluate_integer(ec->handle, "_UID", NULL, &uid);
- if (ec_ecdt && ec_ecdt->uid == uid) {
+ /* XXX we don't test uids, because on some boxes ecdt uid = 0, see:
+ http://bugzilla.kernel.org/show_bug.cgi?id=6111 */
+ if (ec_ecdt) {
acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
- ACPI_ADR_SPACE_EC, &acpi_ec_space_handler);
-
- acpi_remove_gpe_handler(NULL, ec_ecdt->gpe_bit, &acpi_ec_gpe_handler);
+ ACPI_ADR_SPACE_EC,
+ &acpi_ec_space_handler);
+
+ acpi_remove_gpe_handler(NULL, ec_ecdt->gpe,
+ &acpi_ec_gpe_handler);
kfree(ec_ecdt);
}
/* Get GPE bit assignment (EC events). */
/* TODO: Add support for _GPE returning a package */
- status = acpi_evaluate_integer(ec->handle, "_GPE", NULL, &ec->gpe_bit);
+ status = acpi_evaluate_integer(ec->handle, "_GPE", NULL, &ec->gpe);
if (ACPI_FAILURE(status)) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
- "Error obtaining GPE bit assignment\n"));
+ ACPI_EXCEPTION((AE_INFO, status,
+ "Obtaining GPE bit assignment"));
result = -ENODEV;
goto end;
}
if (result)
goto end;
- printk(KERN_INFO PREFIX "%s [%s] (gpe %d)\n",
- acpi_device_name(device), acpi_device_bid(device),
- (u32) ec->gpe_bit);
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s [%s] (gpe %d) interrupt mode.",
+ acpi_device_name(device), acpi_device_bid(device),
+ (u32) ec->gpe));
if (!first_ec)
first_ec = device;
-end:
+ end:
if (result)
kfree(ec);
- return_VALUE(result);
+ return result;
}
-
-static int
-acpi_ec_remove (
- struct acpi_device *device,
- int type)
+static int acpi_ec_remove(struct acpi_device *device, int type)
{
- struct acpi_ec *ec = NULL;
-
- ACPI_FUNCTION_TRACE("acpi_ec_remove");
+ struct acpi_ec *ec = NULL;
if (!device)
- return_VALUE(-EINVAL);
+ return -EINVAL;
ec = acpi_driver_data(device);
kfree(ec);
- return_VALUE(0);
+ return 0;
}
-
static acpi_status
-acpi_ec_io_ports (
- struct acpi_resource *resource,
- void *context)
+acpi_ec_io_ports(struct acpi_resource *resource, void *context)
{
- struct acpi_ec *ec = (struct acpi_ec *) context;
- struct acpi_generic_address *addr;
+ struct acpi_ec *ec = (struct acpi_ec *)context;
- if (resource->id != ACPI_RSTYPE_IO) {
+ if (resource->type != ACPI_RESOURCE_TYPE_IO) {
return AE_OK;
}
* the second address region returned is the status/command
* port.
*/
- if (ec->data_addr.register_bit_width == 0) {
- addr = &ec->data_addr;
- } else if (ec->command_addr.register_bit_width == 0) {
- addr = &ec->command_addr;
+ if (ec->data_addr == 0) {
+ ec->data_addr = resource->data.io.minimum;
+ } else if (ec->command_addr == 0) {
+ ec->command_addr = resource->data.io.minimum;
} else {
return AE_CTRL_TERMINATE;
}
- addr->address_space_id = ACPI_ADR_SPACE_SYSTEM_IO;
- addr->register_bit_width = 8;
- addr->register_bit_offset = 0;
- addr->address = resource->data.io.min_base_address;
-
return AE_OK;
}
-
-static int
-acpi_ec_start (
- struct acpi_device *device)
+static int acpi_ec_start(struct acpi_device *device)
{
- acpi_status status = AE_OK;
- struct acpi_ec *ec = NULL;
-
- ACPI_FUNCTION_TRACE("acpi_ec_start");
+ acpi_status status = AE_OK;
+ struct acpi_ec *ec = NULL;
if (!device)
- return_VALUE(-EINVAL);
+ return -EINVAL;
ec = acpi_driver_data(device);
if (!ec)
- return_VALUE(-EINVAL);
+ return -EINVAL;
/*
* Get I/O port addresses. Convert to GAS format.
*/
status = acpi_walk_resources(ec->handle, METHOD_NAME__CRS,
- acpi_ec_io_ports, ec);
- if (ACPI_FAILURE(status) || ec->command_addr.register_bit_width == 0) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error getting I/O port addresses"));
- return_VALUE(-ENODEV);
+ acpi_ec_io_ports, ec);
+ if (ACPI_FAILURE(status) || ec->command_addr == 0) {
+ ACPI_EXCEPTION((AE_INFO, status,
+ "Error getting I/O port addresses"));
+ return -ENODEV;
}
- ec->status_addr = ec->command_addr;
-
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02x, ports=0x%2x,0x%2x\n",
- (u32) ec->gpe_bit, (u32) ec->command_addr.address,
- (u32) ec->data_addr.address));
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02lx, ports=0x%2lx,0x%2lx",
+ ec->gpe, ec->command_addr, ec->data_addr));
/*
* Install GPE handler
*/
- status = acpi_install_gpe_handler(NULL, ec->gpe_bit,
- ACPI_GPE_EDGE_TRIGGERED, &acpi_ec_gpe_handler, ec);
+ status = acpi_install_gpe_handler(NULL, ec->gpe,
+ ACPI_GPE_EDGE_TRIGGERED,
+ &acpi_ec_gpe_handler, ec);
if (ACPI_FAILURE(status)) {
- return_VALUE(-ENODEV);
+ return -ENODEV;
}
- acpi_set_gpe_type (NULL, ec->gpe_bit, ACPI_GPE_TYPE_RUNTIME);
- acpi_enable_gpe (NULL, ec->gpe_bit, ACPI_NOT_ISR);
+ acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME);
+ acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
- status = acpi_install_address_space_handler (ec->handle,
- ACPI_ADR_SPACE_EC, &acpi_ec_space_handler,
- &acpi_ec_space_setup, ec);
+ status = acpi_install_address_space_handler(ec->handle,
+ ACPI_ADR_SPACE_EC,
+ &acpi_ec_space_handler,
+ &acpi_ec_space_setup, ec);
if (ACPI_FAILURE(status)) {
- acpi_remove_gpe_handler(NULL, ec->gpe_bit, &acpi_ec_gpe_handler);
- return_VALUE(-ENODEV);
+ acpi_remove_gpe_handler(NULL, ec->gpe, &acpi_ec_gpe_handler);
+ return -ENODEV;
}
- return_VALUE(AE_OK);
+ return AE_OK;
}
-
-static int
-acpi_ec_stop (
- struct acpi_device *device,
- int type)
+static int acpi_ec_stop(struct acpi_device *device, int type)
{
- acpi_status status = AE_OK;
- struct acpi_ec *ec = NULL;
-
- ACPI_FUNCTION_TRACE("acpi_ec_stop");
+ acpi_status status = AE_OK;
+ struct acpi_ec *ec = NULL;
if (!device)
- return_VALUE(-EINVAL);
+ return -EINVAL;
ec = acpi_driver_data(device);
status = acpi_remove_address_space_handler(ec->handle,
- ACPI_ADR_SPACE_EC, &acpi_ec_space_handler);
+ ACPI_ADR_SPACE_EC,
+ &acpi_ec_space_handler);
if (ACPI_FAILURE(status))
- return_VALUE(-ENODEV);
+ return -ENODEV;
- status = acpi_remove_gpe_handler(NULL, ec->gpe_bit, &acpi_ec_gpe_handler);
+ status = acpi_remove_gpe_handler(NULL, ec->gpe, &acpi_ec_gpe_handler);
if (ACPI_FAILURE(status))
- return_VALUE(-ENODEV);
+ return -ENODEV;
- return_VALUE(0);
+ return 0;
}
+static acpi_status __init
+acpi_fake_ecdt_callback(acpi_handle handle,
+ u32 Level, void *context, void **retval)
+{
+ acpi_status status;
-int __init
-acpi_ec_ecdt_probe (void)
+ mutex_init(&ec_ecdt->lock);
+ if (acpi_ec_mode == EC_INTR) {
+ init_waitqueue_head(&ec_ecdt->wait);
+ }
+ status = acpi_walk_resources(handle, METHOD_NAME__CRS,
+ acpi_ec_io_ports, ec_ecdt);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ ec_ecdt->uid = -1;
+ acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->uid);
+
+ status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec_ecdt->gpe);
+ if (ACPI_FAILURE(status))
+ return status;
+ ec_ecdt->global_lock = TRUE;
+ ec_ecdt->handle = handle;
+
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02lx, ports=0x%2lx, 0x%2lx",
+ ec_ecdt->gpe, ec_ecdt->command_addr,
+ ec_ecdt->data_addr));
+
+ return AE_CTRL_TERMINATE;
+}
+
+/*
+ * Some BIOS (such as some from Gateway laptops) access EC region very early
+ * such as in BAT0._INI or EC._INI before an EC device is found and
+ * do not provide an ECDT. According to ACPI spec, ECDT isn't mandatorily
+ * required, but if EC regison is accessed early, it is required.
+ * The routine tries to workaround the BIOS bug by pre-scan EC device
+ * It assumes that _CRS, _HID, _GPE, _UID methods of EC don't touch any
+ * op region (since _REG isn't invoked yet). The assumption is true for
+ * all systems found.
+ */
+static int __init acpi_ec_fake_ecdt(void)
+{
+ acpi_status status;
+ int ret = 0;
+
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Try to make an fake ECDT"));
+
+ ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL);
+ if (!ec_ecdt) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ status = acpi_get_devices(ACPI_EC_HID,
+ acpi_fake_ecdt_callback, NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ kfree(ec_ecdt);
+ ec_ecdt = NULL;
+ ret = -ENODEV;
+ ACPI_EXCEPTION((AE_INFO, status, "Can't make an fake ECDT"));
+ goto error;
+ }
+ return 0;
+ error:
+ return ret;
+}
+
+static int __init acpi_ec_get_real_ecdt(void)
{
- acpi_status status;
- struct acpi_table_ecdt *ecdt_ptr;
+ acpi_status status;
+ struct acpi_table_ecdt *ecdt_ptr;
- status = acpi_get_firmware_table("ECDT", 1, ACPI_LOGICAL_ADDRESSING,
- (struct acpi_table_header **) &ecdt_ptr);
+ status = acpi_get_firmware_table("ECDT", 1, ACPI_LOGICAL_ADDRESSING,
+ (struct acpi_table_header **)
+ &ecdt_ptr);
if (ACPI_FAILURE(status))
- return 0;
+ return -ENODEV;
- printk(KERN_INFO PREFIX "Found ECDT\n");
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found ECDT"));
- /*
+ /*
* Generate a temporary ec context to use until the namespace is scanned
*/
- ec_ecdt = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
+ ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL);
if (!ec_ecdt)
return -ENOMEM;
- memset(ec_ecdt, 0, sizeof(struct acpi_ec));
- ec_ecdt->command_addr = ecdt_ptr->ec_control;
- ec_ecdt->status_addr = ecdt_ptr->ec_control;
- ec_ecdt->data_addr = ecdt_ptr->ec_data;
- ec_ecdt->gpe_bit = ecdt_ptr->gpe_bit;
- ec_ecdt->lock = SPIN_LOCK_UNLOCKED;
+ mutex_init(&ec_ecdt->lock);
+ if (acpi_ec_mode == EC_INTR) {
+ init_waitqueue_head(&ec_ecdt->wait);
+ }
+ ec_ecdt->command_addr = ecdt_ptr->ec_control.address;
+ ec_ecdt->data_addr = ecdt_ptr->ec_data.address;
+ ec_ecdt->gpe = ecdt_ptr->gpe_bit;
/* use the GL just to be safe */
ec_ecdt->global_lock = TRUE;
ec_ecdt->uid = ecdt_ptr->uid;
goto error;
}
+ return 0;
+ error:
+ ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
+ kfree(ec_ecdt);
+ ec_ecdt = NULL;
+
+ return -ENODEV;
+}
+
+static int __initdata acpi_fake_ecdt_enabled;
+int __init acpi_ec_ecdt_probe(void)
+{
+ acpi_status status;
+ int ret;
+
+ ret = acpi_ec_get_real_ecdt();
+ /* Try to make a fake ECDT */
+ if (ret && acpi_fake_ecdt_enabled) {
+ ret = acpi_ec_fake_ecdt();
+ }
+
+ if (ret)
+ return 0;
+
/*
* Install GPE handler
*/
- status = acpi_install_gpe_handler(NULL, ec_ecdt->gpe_bit,
- ACPI_GPE_EDGE_TRIGGERED, &acpi_ec_gpe_handler,
- ec_ecdt);
+ status = acpi_install_gpe_handler(NULL, ec_ecdt->gpe,
+ ACPI_GPE_EDGE_TRIGGERED,
+ &acpi_ec_gpe_handler, ec_ecdt);
if (ACPI_FAILURE(status)) {
goto error;
}
- acpi_set_gpe_type (NULL, ec_ecdt->gpe_bit, ACPI_GPE_TYPE_RUNTIME);
- acpi_enable_gpe (NULL, ec_ecdt->gpe_bit, ACPI_NOT_ISR);
-
- status = acpi_install_address_space_handler (ACPI_ROOT_OBJECT,
- ACPI_ADR_SPACE_EC, &acpi_ec_space_handler,
- &acpi_ec_space_setup, ec_ecdt);
+ acpi_set_gpe_type(NULL, ec_ecdt->gpe, ACPI_GPE_TYPE_RUNTIME);
+ acpi_enable_gpe(NULL, ec_ecdt->gpe, ACPI_NOT_ISR);
+
+ status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_EC,
+ &acpi_ec_space_handler,
+ &acpi_ec_space_setup,
+ ec_ecdt);
if (ACPI_FAILURE(status)) {
- acpi_remove_gpe_handler(NULL, ec_ecdt->gpe_bit,
- &acpi_ec_gpe_handler);
+ acpi_remove_gpe_handler(NULL, ec_ecdt->gpe,
+ &acpi_ec_gpe_handler);
goto error;
}
return 0;
-error:
- printk(KERN_ERR PREFIX "Could not use ECDT\n");
+ error:
+ ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
kfree(ec_ecdt);
ec_ecdt = NULL;
return -ENODEV;
}
-
-static int __init acpi_ec_init (void)
+static int __init acpi_ec_init(void)
{
- int result = 0;
-
- ACPI_FUNCTION_TRACE("acpi_ec_init");
+ int result = 0;
if (acpi_disabled)
- return_VALUE(0);
+ return 0;
acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
if (!acpi_ec_dir)
- return_VALUE(-ENODEV);
+ return -ENODEV;
/* Now register the driver for the EC */
result = acpi_bus_register_driver(&acpi_ec_driver);
if (result < 0) {
remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
- return_VALUE(-ENODEV);
+ return -ENODEV;
}
- return_VALUE(result);
+ return result;
}
subsys_initcall(acpi_ec_init);
/* EC driver currently not unloadable */
#if 0
-static void __exit
-acpi_ec_exit (void)
+static void __exit acpi_ec_exit(void)
{
- ACPI_FUNCTION_TRACE("acpi_ec_exit");
acpi_bus_unregister_driver(&acpi_ec_driver);
remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
- return_VOID;
+ return;
+}
+#endif /* 0 */
+
+static int __init acpi_fake_ecdt_setup(char *str)
+{
+ acpi_fake_ecdt_enabled = 1;
+ return 1;
+}
+
+__setup("acpi_fake_ecdt", acpi_fake_ecdt_setup);
+static int __init acpi_ec_set_intr_mode(char *str)
+{
+ int intr;
+
+ if (!get_option(&str, &intr))
+ return 0;
+
+ if (intr) {
+ acpi_ec_mode = EC_INTR;
+ } else {
+ acpi_ec_mode = EC_POLL;
+ }
+ acpi_ec_driver.ops.add = acpi_ec_add;
+ printk(KERN_NOTICE PREFIX "%s mode.\n",
+ intr ? "interrupt" : "polling");
+
+ return 1;
}
-#endif /* 0 */
+__setup("ec_intr=", acpi_ec_set_intr_mode);