OpenCloudOS-Kernel/drivers/acpi/ec.c

1438 lines
35 KiB
C
Raw Normal View History

/*
* 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>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <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")
#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"
#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 */
#define ACPI_EC_EVENT_OBF 0x01 /* Output buffer full */
#define ACPI_EC_EVENT_IBE 0x02 /* Input buffer empty */
#define ACPI_EC_DELAY 50 /* Wait 50ms max. during EC ops */
#define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */
#define ACPI_EC_UDELAY 100 /* Poll @ 100us increments */
#define ACPI_EC_UDELAY_COUNT 1000 /* Wait 10ms max. during EC ops */
#define ACPI_EC_COMMAND_READ 0x80
#define ACPI_EC_COMMAND_WRITE 0x81
#define ACPI_EC_BURST_ENABLE 0x82
#define ACPI_EC_BURST_DISABLE 0x83
#define ACPI_EC_COMMAND_QUERY 0x84
#define EC_POLL 0xFF
#define EC_INTR 0x00
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_intr_add(struct acpi_device *device);
static int acpi_ec_poll_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_intr_add,
.remove = acpi_ec_remove,
.start = acpi_ec_start,
.stop = acpi_ec_stop,
},
};
union acpi_ec {
struct {
u32 mode;
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;
} common;
struct {
u32 mode;
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;
u8 expect_event;
atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */
atomic_t pending_gpe;
struct semaphore sem;
wait_queue_head_t wait;
} intr;
struct {
u32 mode;
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;
struct semaphore sem;
} poll;
};
static int acpi_ec_poll_wait(union acpi_ec *ec, u8 event);
static int acpi_ec_intr_wait(union acpi_ec *ec, u8 event);
static int acpi_ec_poll_transaction(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len);
static int acpi_ec_intr_transaction(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len);
static void acpi_ec_gpe_poll_query(void *ec_cxt);
static void acpi_ec_gpe_intr_query(void *ec_cxt);
static u32 acpi_ec_gpe_poll_handler(void *data);
static u32 acpi_ec_gpe_intr_handler(void *data);
static acpi_status __init
acpi_fake_ecdt_poll_callback(acpi_handle handle,
u32 Level, void *context, void **retval);
static acpi_status __init
acpi_fake_ecdt_intr_callback(acpi_handle handle,
u32 Level, void *context, void **retval);
static int __init acpi_ec_poll_get_real_ecdt(void);
static int __init acpi_ec_intr_get_real_ecdt(void);
/* If we find an EC via the ECDT, we need to keep a ptr to its context */
static union acpi_ec *ec_ecdt;
/* External interfaces use first EC only, so remember */
static struct acpi_device *first_ec;
static int acpi_ec_poll_mode = EC_INTR;
/* --------------------------------------------------------------------------
Transaction Management
-------------------------------------------------------------------------- */
static u32 acpi_ec_read_status(union acpi_ec *ec)
{
u32 status = 0;
acpi_hw_low_level_read(8, &status, &ec->common.status_addr);
return status;
}
static int acpi_ec_check_status(u32 status, u8 event) {
switch (event) {
case ACPI_EC_EVENT_OBF:
if (status & ACPI_EC_FLAG_OBF)
return 1;
case ACPI_EC_EVENT_IBE:
if (!(status & ACPI_EC_FLAG_IBF))
return 1;
default:
break;
}
return 0;
}
static int acpi_ec_wait(union acpi_ec *ec, u8 event)
{
if (acpi_ec_poll_mode)
return acpi_ec_poll_wait(ec, event);
else
return acpi_ec_intr_wait(ec, event);
}
static int acpi_ec_poll_wait(union acpi_ec *ec, u8 event)
{
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->common.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->common.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;
}
static int acpi_ec_intr_wait(union acpi_ec *ec, u8 event)
{
long time_left;
ec->intr.expect_event = event;
if (acpi_ec_check_status(acpi_ec_read_status(ec), event)) {
ec->intr.expect_event = 0;
return 0;
}
time_left = wait_event_timeout(ec->intr.wait, !ec->intr.expect_event,
msecs_to_jiffies(ACPI_EC_DELAY));
ec->intr.expect_event = 0;
if (time_left <= 0) {
if (acpi_ec_check_status(acpi_ec_read_status(ec), event)) {
return 0;
}
} else {
return 0;
}
return -ETIME;
}
#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(union acpi_ec *ec)
{
u32 tmp = 0;
int status = 0;
status = acpi_ec_read_status(ec);
if (status != -EINVAL && !(status & ACPI_EC_FLAG_BURST)) {
status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (status)
goto end;
acpi_hw_low_level_write(8, ACPI_EC_BURST_ENABLE,
&ec->common.command_addr);
status = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
acpi_hw_low_level_read(8, &tmp, &ec->common.data_addr);
if (tmp != 0x90) { /* Burst ACK byte */
return -EINVAL;
}
}
atomic_set(&ec->intr.leaving_burst, 0);
return 0;
end:
ACPI_EXCEPTION ((AE_INFO, status, "EC wait, burst mode");
return -1;
}
int acpi_ec_leave_burst_mode(union acpi_ec *ec)
{
int status = 0;
status = acpi_ec_read_status(ec);
if (status != -EINVAL && (status & ACPI_EC_FLAG_BURST)){
status = acpi_ec_wait(ec, ACPI_EC_FLAG_IBF);
if(status)
goto end;
acpi_hw_low_level_write(8, ACPI_EC_BURST_DISABLE, &ec->common.command_addr);
acpi_ec_wait(ec, ACPI_EC_FLAG_IBF);
}
atomic_set(&ec->intr.leaving_burst, 1);
return 0;
end:
ACPI_EXCEPTION((AE_INFO, status, "EC leave burst mode");
return -1;
}
#endif /* ACPI_FUTURE_USAGE */
static int acpi_ec_transaction(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len)
{
if (acpi_ec_poll_mode)
return acpi_ec_poll_transaction(ec, command, wdata, wdata_len, rdata, rdata_len);
else
return acpi_ec_intr_transaction(ec, command, wdata, wdata_len, rdata, rdata_len);
}
static int acpi_ec_read(union acpi_ec *ec, u8 address, u32 * 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(union 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);
}
static int acpi_ec_transaction_unlocked(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len)
{
int result;
acpi_hw_low_level_write(8, command, &ec->common.command_addr);
for (; wdata_len > 0; wdata_len --) {
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (result)
return result;
acpi_hw_low_level_write(8, *(wdata++), &ec->common.data_addr);
}
if (command == ACPI_EC_COMMAND_WRITE) {
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (result)
return result;
}
for (; rdata_len > 0; rdata_len --) {
u32 d;
result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF);
if (result)
return result;
acpi_hw_low_level_read(8, &d, &ec->common.data_addr);
*(rdata++) = (u8) d;
}
return 0;
}
static int acpi_ec_poll_transaction(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len)
{
acpi_status status = AE_OK;
int result;
u32 glk = 0;
if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata))
return -EINVAL;
if (rdata)
memset(rdata, 0, rdata_len);
if (ec->common.global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
return -ENODEV;
}
if (down_interruptible(&ec->poll.sem)) {
result = -ERESTARTSYS;
goto end_nosem;
}
result = acpi_ec_transaction_unlocked(ec, command,
wdata, wdata_len,
rdata, rdata_len);
up(&ec->poll.sem);
end_nosem:
if (ec->common.global_lock)
acpi_release_global_lock(glk);
return result;
}
static int acpi_ec_intr_transaction(union acpi_ec *ec, u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len)
{
int status;
u32 glk;
if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata))
return -EINVAL;
if (rdata)
memset(rdata, 0, rdata_len);
if (ec->common.global_lock) {
status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
if (ACPI_FAILURE(status))
return -ENODEV;
}
WARN_ON(in_interrupt());
down(&ec->intr.sem);
status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBE);
if (status) {
printk(KERN_DEBUG PREFIX "read EC, IB not empty\n");
goto end;
}
status = acpi_ec_transaction_unlocked(ec, command,
wdata, wdata_len,
rdata, rdata_len);
end:
up(&ec->intr.sem);
if (ec->common.global_lock)
acpi_release_global_lock(glk);
return status;
}
/*
* Externally callable EC access functions. For now, assume 1 EC only
*/
int ec_read(u8 addr, u8 * val)
{
union acpi_ec *ec;
int err;
u32 temp_data;
if (!first_ec)
return -ENODEV;
ec = acpi_driver_data(first_ec);
err = acpi_ec_read(ec, addr, &temp_data);
if (!err) {
*val = temp_data;
return 0;
} else
return err;
}
EXPORT_SYMBOL(ec_read);
int ec_write(u8 addr, u8 val)
{
union acpi_ec *ec;
int err;
if (!first_ec)
return -ENODEV;
ec = acpi_driver_data(first_ec);
err = acpi_ec_write(ec, addr, val);
return err;
}
EXPORT_SYMBOL(ec_write);
extern int ec_transaction(u8 command,
const u8 *wdata, unsigned wdata_len,
u8 *rdata, unsigned rdata_len)
{
union acpi_ec *ec;
if (!first_ec)
return -ENODEV;
ec = acpi_driver_data(first_ec);
return acpi_ec_transaction(ec, command, wdata, wdata_len, rdata, rdata_len);
}
EXPORT_SYMBOL(ec_transaction);
static int acpi_ec_query(union acpi_ec *ec, u32 * 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).
*/
result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_QUERY, NULL, 0, &d, 1);
if (result)
return result;
if (!d)
return -ENODATA;
*data = d;
return 0;
}
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
union acpi_ec_query_data {
acpi_handle handle;
u8 data;
};
static void acpi_ec_gpe_query(void *ec_cxt)
{
if (acpi_ec_poll_mode)
acpi_ec_gpe_poll_query(ec_cxt);
else
acpi_ec_gpe_intr_query(ec_cxt);
}
static void acpi_ec_gpe_poll_query(void *ec_cxt)
{
union acpi_ec *ec = (union acpi_ec *)ec_cxt;
u32 value = 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'
};
if (!ec_cxt)
goto end;
if (down_interruptible (&ec->poll.sem)) {
return;
}
acpi_hw_low_level_read(8, &value, &ec->common.command_addr);
up(&ec->poll.sem);
/* 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 (acpi_ec_query(ec, &value))
goto end;
object_name[2] = hex[((value >> 4) & 0x0F)];
object_name[3] = hex[(value & 0x0F)];
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s\n", object_name));
acpi_evaluate_object(ec->common.handle, object_name, NULL, NULL);
end:
acpi_enable_gpe(NULL, ec->common.gpe_bit, ACPI_NOT_ISR);
}
static void acpi_ec_gpe_intr_query(void *ec_cxt)
{
union acpi_ec *ec = (union acpi_ec *)ec_cxt;
u32 value;
int result = -ENODATA;
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'
};
if (acpi_ec_read_status(ec) & ACPI_EC_FLAG_SCI)
result = acpi_ec_query(ec, &value);
if (result)
goto end;
object_name[2] = hex[((value >> 4) & 0x0F)];
object_name[3] = hex[(value & 0x0F)];
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Evaluating %s\n", object_name));
acpi_evaluate_object(ec->common.handle, object_name, NULL, NULL);
end:
atomic_dec(&ec->intr.pending_gpe);
return;
}
static u32 acpi_ec_gpe_handler(void *data)
{
if (acpi_ec_poll_mode)
return acpi_ec_gpe_poll_handler(data);
else
return acpi_ec_gpe_intr_handler(data);
}
static u32 acpi_ec_gpe_poll_handler(void *data)
{
acpi_status status = AE_OK;
union acpi_ec *ec = (union acpi_ec *)data;
if (!ec)
return ACPI_INTERRUPT_NOT_HANDLED;
acpi_disable_gpe(NULL, ec->common.gpe_bit, ACPI_ISR);
status = acpi_os_execute(OSL_EC_POLL_HANDLER, acpi_ec_gpe_query, ec);
if (status == AE_OK)
return ACPI_INTERRUPT_HANDLED;
else
return ACPI_INTERRUPT_NOT_HANDLED;
}
static u32 acpi_ec_gpe_intr_handler(void *data)
{
acpi_status status = AE_OK;
u32 value;
union acpi_ec *ec = (union acpi_ec *)data;
if (!ec)
return ACPI_INTERRUPT_NOT_HANDLED;
acpi_clear_gpe(NULL, ec->common.gpe_bit, ACPI_ISR);
value = acpi_ec_read_status(ec);
switch (ec->intr.expect_event) {
case ACPI_EC_EVENT_OBF:
if (!(value & ACPI_EC_FLAG_OBF))
break;
ec->intr.expect_event = 0;
wake_up(&ec->intr.wait);
break;
case ACPI_EC_EVENT_IBE:
if ((value & ACPI_EC_FLAG_IBF))
break;
ec->intr.expect_event = 0;
wake_up(&ec->intr.wait);
break;
default:
break;
}
if (value & ACPI_EC_FLAG_SCI) {
atomic_add(1, &ec->intr.pending_gpe);
status = acpi_os_execute(OSL_EC_BURST_HANDLER,
acpi_ec_gpe_query, ec);
return status == AE_OK ?
ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
}
acpi_enable_gpe(NULL, ec->common.gpe_bit, ACPI_ISR);
return status == AE_OK ?
ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED;
}
/* --------------------------------------------------------------------------
Address Space Management
-------------------------------------------------------------------------- */
static acpi_status
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.
*/
*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)
{
int result = 0;
union acpi_ec *ec = NULL;
u64 temp = *value;
acpi_integer f_v = 0;
int i = 0;
if ((address > 0xFF) || !value || !handler_context)
return AE_BAD_PARAMETER;
if (bit_width != 8 && acpi_strict) {
printk(KERN_WARNING PREFIX
"acpi_ec_space_handler: bit_width should be 8\n");
return AE_BAD_PARAMETER;
}
ec = (union acpi_ec *)handler_context;
next_byte:
switch (function) {
case ACPI_READ:
temp = 0;
result = acpi_ec_read(ec, (u8) address, (u32 *) & temp);
break;
case ACPI_WRITE:
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 AE_BAD_PARAMETER;
break;
case -ENODEV:
return AE_NOT_FOUND;
break;
case -ETIME:
return AE_TIME;
break;
default:
return AE_OK;
}
}
/* --------------------------------------------------------------------------
FS Interface (/proc)
-------------------------------------------------------------------------- */
static struct proc_dir_entry *acpi_ec_dir;
static int acpi_ec_read_info(struct seq_file *seq, void *offset)
{
union acpi_ec *ec = (union acpi_ec *)seq->private;
if (!ec)
goto end;
seq_printf(seq, "gpe bit: 0x%02x\n",
(u32) ec->common.gpe_bit);
seq_printf(seq, "ports: 0x%02x, 0x%02x\n",
(u32) ec->common.status_addr.address,
(u32) ec->common.data_addr.address);
seq_printf(seq, "use global lock: %s\n",
ec->common.global_lock ? "yes" : "no");
acpi_enable_gpe(NULL, ec->common.gpe_bit, ACPI_NOT_ISR);
end:
return 0;
}
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 const 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)
{
struct proc_dir_entry *entry = NULL;
if (!acpi_device_dir(device)) {
acpi_device_dir(device) = proc_mkdir(acpi_device_bid(device),
acpi_ec_dir);
if (!acpi_device_dir(device))
return -ENODEV;
}
entry = create_proc_entry(ACPI_EC_FILE_INFO, S_IRUGO,
acpi_device_dir(device));
if (!entry)
return -ENODEV;
else {
entry->proc_fops = &acpi_ec_info_ops;
entry->data = acpi_driver_data(device);
entry->owner = THIS_MODULE;
}
return 0;
}
static int acpi_ec_remove_fs(struct acpi_device *device)
{
if (acpi_device_dir(device)) {
remove_proc_entry(ACPI_EC_FILE_INFO, acpi_device_dir(device));
remove_proc_entry(acpi_device_bid(device), acpi_ec_dir);
acpi_device_dir(device) = NULL;
}
return 0;
}
/* --------------------------------------------------------------------------
Driver Interface
-------------------------------------------------------------------------- */
static int acpi_ec_poll_add(struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
union acpi_ec *ec = NULL;
if (!device)
return -EINVAL;
ec = kmalloc(sizeof(union acpi_ec), GFP_KERNEL);
if (!ec)
return -ENOMEM;
memset(ec, 0, sizeof(union acpi_ec));
ec->common.handle = device->handle;
ec->common.uid = -1;
init_MUTEX(&ec->poll.sem);
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->common.handle, "_GLK", NULL,
&ec->common.global_lock);
/* 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->common.gpe_bit,
&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->common.handle, "_GPE", NULL,
&ec->common.gpe_bit);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "Obtaining GPE bit"));
result = -ENODEV;
goto end;
}
result = acpi_ec_add_fs(device);
if (result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (gpe %d) polling mode.\n",
acpi_device_name(device), acpi_device_bid(device),
(u32) ec->common.gpe_bit);
if (!first_ec)
first_ec = device;
end:
if (result)
kfree(ec);
return result;
}
static int acpi_ec_intr_add(struct acpi_device *device)
{
int result = 0;
acpi_status status = AE_OK;
union acpi_ec *ec = NULL;
if (!device)
return -EINVAL;
ec = kmalloc(sizeof(union acpi_ec), GFP_KERNEL);
if (!ec)
return -ENOMEM;
memset(ec, 0, sizeof(union acpi_ec));
ec->common.handle = device->handle;
ec->common.uid = -1;
atomic_set(&ec->intr.pending_gpe, 0);
atomic_set(&ec->intr.leaving_burst, 1);
init_MUTEX(&ec->intr.sem);
init_waitqueue_head(&ec->intr.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->common.handle, "_GLK", NULL,
&ec->common.global_lock);
/* 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->common.gpe_bit,
&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->common.handle, "_GPE", NULL,
&ec->common.gpe_bit);
if (ACPI_FAILURE(status)) {
printk(KERN_ERR PREFIX "Obtaining GPE bit assignment\n");
result = -ENODEV;
goto end;
}
result = acpi_ec_add_fs(device);
if (result)
goto end;
printk(KERN_INFO PREFIX "%s [%s] (gpe %d) interrupt mode.\n",
acpi_device_name(device), acpi_device_bid(device),
(u32) ec->common.gpe_bit);
if (!first_ec)
first_ec = device;
end:
if (result)
kfree(ec);
return result;
}
static int acpi_ec_remove(struct acpi_device *device, int type)
{
union acpi_ec *ec = NULL;
if (!device)
return -EINVAL;
ec = acpi_driver_data(device);
acpi_ec_remove_fs(device);
kfree(ec);
return 0;
}
static acpi_status
acpi_ec_io_ports(struct acpi_resource *resource, void *context)
{
union acpi_ec *ec = (union acpi_ec *)context;
struct acpi_generic_address *addr;
[ACPI] ACPICA 20050930 Completed a major overhaul of the Resource Manager code - specifically, optimizations in the area of the AML/internal resource conversion code. The code has been optimized to simplify and eliminate duplicated code, CPU stack use has been decreased by optimizing function parameters and local variables, and naming conventions across the manager have been standardized for clarity and ease of maintenance (this includes function, parameter, variable, and struct/typedef names.) All Resource Manager dispatch and information tables have been moved to a single location for clarity and ease of maintenance. One new file was created, named "rsinfo.c". The ACPI return macros (return_ACPI_STATUS, etc.) have been modified to guarantee that the argument is not evaluated twice, making them less prone to macro side-effects. However, since there exists the possibility of additional stack use if a particular compiler cannot optimize them (such as in the debug generation case), the original macros are optionally available. Note that some invocations of the return_VALUE macro may now cause size mismatch warnings; the return_UINT8 and return_UINT32 macros are provided to eliminate these. (From Randy Dunlap) Implemented a new mechanism to enable debug tracing for individual control methods. A new external interface, acpi_debug_trace(), is provided to enable this mechanism. The intent is to allow the host OS to easily enable and disable tracing for problematic control methods. This interface can be easily exposed to a user or debugger interface if desired. See the file psxface.c for details. acpi_ut_callocate() will now return a valid pointer if a length of zero is specified - a length of one is used and a warning is issued. This matches the behavior of acpi_ut_allocate(). Signed-off-by: Bob Moore <robert.moore@intel.com> Signed-off-by: Len Brown <len.brown@intel.com>
2005-10-01 07:03:00 +08:00
if (resource->type != ACPI_RESOURCE_TYPE_IO) {
return AE_OK;
}
/*
* The first address region returned is the data port, and
* the second address region returned is the status/command
* port.
*/
if (ec->common.data_addr.register_bit_width == 0) {
addr = &ec->common.data_addr;
} else if (ec->common.command_addr.register_bit_width == 0) {
addr = &ec->common.command_addr;
} else {
return AE_CTRL_TERMINATE;
}
addr->address_space_id = ACPI_ADR_SPACE_SYSTEM_IO;
addr->register_bit_width = 8;
addr->register_bit_offset = 0;
[ACPI] ACPICA 20050930 Completed a major overhaul of the Resource Manager code - specifically, optimizations in the area of the AML/internal resource conversion code. The code has been optimized to simplify and eliminate duplicated code, CPU stack use has been decreased by optimizing function parameters and local variables, and naming conventions across the manager have been standardized for clarity and ease of maintenance (this includes function, parameter, variable, and struct/typedef names.) All Resource Manager dispatch and information tables have been moved to a single location for clarity and ease of maintenance. One new file was created, named "rsinfo.c". The ACPI return macros (return_ACPI_STATUS, etc.) have been modified to guarantee that the argument is not evaluated twice, making them less prone to macro side-effects. However, since there exists the possibility of additional stack use if a particular compiler cannot optimize them (such as in the debug generation case), the original macros are optionally available. Note that some invocations of the return_VALUE macro may now cause size mismatch warnings; the return_UINT8 and return_UINT32 macros are provided to eliminate these. (From Randy Dunlap) Implemented a new mechanism to enable debug tracing for individual control methods. A new external interface, acpi_debug_trace(), is provided to enable this mechanism. The intent is to allow the host OS to easily enable and disable tracing for problematic control methods. This interface can be easily exposed to a user or debugger interface if desired. See the file psxface.c for details. acpi_ut_callocate() will now return a valid pointer if a length of zero is specified - a length of one is used and a warning is issued. This matches the behavior of acpi_ut_allocate(). Signed-off-by: Bob Moore <robert.moore@intel.com> Signed-off-by: Len Brown <len.brown@intel.com>
2005-10-01 07:03:00 +08:00
addr->address = resource->data.io.minimum;
return AE_OK;
}
static int acpi_ec_start(struct acpi_device *device)
{
acpi_status status = AE_OK;
union acpi_ec *ec = NULL;
if (!device)
return -EINVAL;
ec = acpi_driver_data(device);
if (!ec)
return -EINVAL;
/*
* Get I/O port addresses. Convert to GAS format.
*/
status = acpi_walk_resources(ec->common.handle, METHOD_NAME__CRS,
acpi_ec_io_ports, ec);
if (ACPI_FAILURE(status)
|| ec->common.command_addr.register_bit_width == 0) {
printk(KERN_ERR PREFIX "Error getting I/O port addresses\n");
return -ENODEV;
}
ec->common.status_addr = ec->common.command_addr;
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "gpe=0x%02x, ports=0x%2x,0x%2x\n",
(u32) ec->common.gpe_bit,
(u32) ec->common.command_addr.address,
(u32) ec->common.data_addr.address));
/*
* Install GPE handler
*/
status = acpi_install_gpe_handler(NULL, ec->common.gpe_bit,
ACPI_GPE_EDGE_TRIGGERED,
&acpi_ec_gpe_handler, ec);
if (ACPI_FAILURE(status)) {
return -ENODEV;
}
acpi_set_gpe_type(NULL, ec->common.gpe_bit, ACPI_GPE_TYPE_RUNTIME);
acpi_enable_gpe(NULL, ec->common.gpe_bit, ACPI_NOT_ISR);
status = acpi_install_address_space_handler(ec->common.handle,
ACPI_ADR_SPACE_EC,
&acpi_ec_space_handler,
&acpi_ec_space_setup, ec);
if (ACPI_FAILURE(status)) {
acpi_remove_gpe_handler(NULL, ec->common.gpe_bit,
&acpi_ec_gpe_handler);
return -ENODEV;
}
return AE_OK;
}
static int acpi_ec_stop(struct acpi_device *device, int type)
{
acpi_status status = AE_OK;
union acpi_ec *ec = NULL;
if (!device)
return -EINVAL;
ec = acpi_driver_data(device);
status = acpi_remove_address_space_handler(ec->common.handle,
ACPI_ADR_SPACE_EC,
&acpi_ec_space_handler);
if (ACPI_FAILURE(status))
return -ENODEV;
status =
acpi_remove_gpe_handler(NULL, ec->common.gpe_bit,
&acpi_ec_gpe_handler);
if (ACPI_FAILURE(status))
return -ENODEV;
return 0;
}
static acpi_status __init
acpi_fake_ecdt_callback(acpi_handle handle,
u32 Level, void *context, void **retval)
{
if (acpi_ec_poll_mode)
return acpi_fake_ecdt_poll_callback(handle,
Level, context, retval);
else
return acpi_fake_ecdt_intr_callback(handle,
Level, context, retval);
}
static acpi_status __init
acpi_fake_ecdt_poll_callback(acpi_handle handle,
u32 Level, void *context, void **retval)
{
acpi_status status;
status = acpi_walk_resources(handle, METHOD_NAME__CRS,
acpi_ec_io_ports, ec_ecdt);
if (ACPI_FAILURE(status))
return status;
ec_ecdt->common.status_addr = ec_ecdt->common.command_addr;
ec_ecdt->common.uid = -1;
acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->common.uid);
status =
acpi_evaluate_integer(handle, "_GPE", NULL,
&ec_ecdt->common.gpe_bit);
if (ACPI_FAILURE(status))
return status;
init_MUTEX(&ec_ecdt->poll.sem);
ec_ecdt->common.global_lock = TRUE;
ec_ecdt->common.handle = handle;
printk(KERN_INFO PREFIX "GPE=0x%02x, ports=0x%2x, 0x%2x\n",
(u32) ec_ecdt->common.gpe_bit,
(u32) ec_ecdt->common.command_addr.address,
(u32) ec_ecdt->common.data_addr.address);
return AE_CTRL_TERMINATE;
}
static acpi_status __init
acpi_fake_ecdt_intr_callback(acpi_handle handle,
u32 Level, void *context, void **retval)
{
acpi_status status;
init_MUTEX(&ec_ecdt->intr.sem);
init_waitqueue_head(&ec_ecdt->intr.wait);
status = acpi_walk_resources(handle, METHOD_NAME__CRS,
acpi_ec_io_ports, ec_ecdt);
if (ACPI_FAILURE(status))
return status;
ec_ecdt->common.status_addr = ec_ecdt->common.command_addr;
ec_ecdt->common.uid = -1;
acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->common.uid);
status =
acpi_evaluate_integer(handle, "_GPE", NULL,
&ec_ecdt->common.gpe_bit);
if (ACPI_FAILURE(status))
return status;
ec_ecdt->common.global_lock = TRUE;
ec_ecdt->common.handle = handle;
printk(KERN_INFO PREFIX "GPE=0x%02x, ports=0x%2x, 0x%2x\n",
(u32) ec_ecdt->common.gpe_bit,
(u32) ec_ecdt->common.command_addr.address,
(u32) ec_ecdt->common.data_addr.address);
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;
printk(KERN_INFO PREFIX "Try to make an fake ECDT\n");
ec_ecdt = kmalloc(sizeof(union acpi_ec), GFP_KERNEL);
if (!ec_ecdt) {
ret = -ENOMEM;
goto error;
}
memset(ec_ecdt, 0, sizeof(union acpi_ec));
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;
goto error;
}
return 0;
error:
printk(KERN_ERR PREFIX "Can't make an fake ECDT\n");
return ret;
}
static int __init acpi_ec_get_real_ecdt(void)
{
if (acpi_ec_poll_mode)
return acpi_ec_poll_get_real_ecdt();
else
return acpi_ec_intr_get_real_ecdt();
}
static int __init acpi_ec_poll_get_real_ecdt(void)
{
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);
if (ACPI_FAILURE(status))
return -ENODEV;
printk(KERN_INFO PREFIX "Found ECDT\n");
/*
* Generate a temporary ec context to use until the namespace is scanned
*/
ec_ecdt = kmalloc(sizeof(union acpi_ec), GFP_KERNEL);
if (!ec_ecdt)
return -ENOMEM;
memset(ec_ecdt, 0, sizeof(union acpi_ec));
ec_ecdt->common.command_addr = ecdt_ptr->ec_control;
ec_ecdt->common.status_addr = ecdt_ptr->ec_control;
ec_ecdt->common.data_addr = ecdt_ptr->ec_data;
ec_ecdt->common.gpe_bit = ecdt_ptr->gpe_bit;
init_MUTEX(&ec_ecdt->poll.sem);
/* use the GL just to be safe */
ec_ecdt->common.global_lock = TRUE;
ec_ecdt->common.uid = ecdt_ptr->uid;
status =
acpi_get_handle(NULL, ecdt_ptr->ec_id, &ec_ecdt->common.handle);
if (ACPI_FAILURE(status)) {
goto error;
}
return 0;
error:
printk(KERN_ERR PREFIX "Could not use ECDT\n");
kfree(ec_ecdt);
ec_ecdt = NULL;
return -ENODEV;
}
static int __init acpi_ec_intr_get_real_ecdt(void)
{
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);
if (ACPI_FAILURE(status))
return -ENODEV;
printk(KERN_INFO PREFIX "Found ECDT\n");
/*
* Generate a temporary ec context to use until the namespace is scanned
*/
ec_ecdt = kmalloc(sizeof(union acpi_ec), GFP_KERNEL);
if (!ec_ecdt)
return -ENOMEM;
memset(ec_ecdt, 0, sizeof(union acpi_ec));
init_MUTEX(&ec_ecdt->intr.sem);
init_waitqueue_head(&ec_ecdt->intr.wait);
ec_ecdt->common.command_addr = ecdt_ptr->ec_control;
ec_ecdt->common.status_addr = ecdt_ptr->ec_control;
ec_ecdt->common.data_addr = ecdt_ptr->ec_data;
ec_ecdt->common.gpe_bit = ecdt_ptr->gpe_bit;
/* use the GL just to be safe */
ec_ecdt->common.global_lock = TRUE;
ec_ecdt->common.uid = ecdt_ptr->uid;
status =
acpi_get_handle(NULL, ecdt_ptr->ec_id, &ec_ecdt->common.handle);
if (ACPI_FAILURE(status)) {
goto error;
}
return 0;
error:
printk(KERN_ERR PREFIX "Could not use ECDT\n");
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->common.gpe_bit,
ACPI_GPE_EDGE_TRIGGERED,
&acpi_ec_gpe_handler, ec_ecdt);
if (ACPI_FAILURE(status)) {
goto error;
}
acpi_set_gpe_type(NULL, ec_ecdt->common.gpe_bit, ACPI_GPE_TYPE_RUNTIME);
acpi_enable_gpe(NULL, ec_ecdt->common.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);
if (ACPI_FAILURE(status)) {
acpi_remove_gpe_handler(NULL, ec_ecdt->common.gpe_bit,
&acpi_ec_gpe_handler);
goto error;
}
return 0;
error:
printk(KERN_ERR PREFIX "Could not use ECDT\n");
kfree(ec_ecdt);
ec_ecdt = NULL;
return -ENODEV;
}
static int __init acpi_ec_init(void)
{
int result = 0;
if (acpi_disabled)
return 0;
acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
if (!acpi_ec_dir)
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 -ENODEV;
}
return result;
}
subsys_initcall(acpi_ec_init);
/* EC driver currently not unloadable */
#if 0
static void __exit acpi_ec_exit(void)
{
acpi_bus_unregister_driver(&acpi_ec_driver);
remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);
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_poll_mode = EC_INTR;
acpi_ec_driver.ops.add = acpi_ec_intr_add;
} else {
acpi_ec_poll_mode = EC_POLL;
acpi_ec_driver.ops.add = acpi_ec_poll_add;
}
printk(KERN_INFO PREFIX "EC %s mode.\n", intr ? "interrupt" : "polling");
return 1;
}
__setup("ec_intr=", acpi_ec_set_intr_mode);