Subject: [PATCH] [acpi ec] Add support for creating early EC devices - Create drivers/acpi/drivers/ec/ecdt.c - Add acpi_ec_ecdt_probe(), which is called by the ACPI bus driver - Check for real ECDT and create EC device based on that, if it is there - If not, and flag is set, create a fake EC device - Add ec_ecdt_exit(), which is called on device add, to free the ecdt-based device (fake or real), when the first EC device is discovered. Signed-off-by: Patrick Mochel --- drivers/acpi/drivers/ec/Makefile | 2 drivers/acpi/drivers/ec/driver.c | 5 + drivers/acpi/drivers/ec/ec.h | 5 + drivers/acpi/drivers/ec/ecdt.c | 157 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 1 deletions(-) create mode 100644 drivers/acpi/drivers/ec/ecdt.c applies-to: 43845432aee8220238a2f5cb7aca7f4c4a3dd571 4700b4f3e86b34446587310cb9639088eec31ccc diff --git a/drivers/acpi/drivers/ec/Makefile b/drivers/acpi/drivers/ec/Makefile index 0de3770..4cd2f89 100644 --- a/drivers/acpi/drivers/ec/Makefile +++ b/drivers/acpi/drivers/ec/Makefile @@ -1,7 +1,7 @@ obj-$(CONFIG_ACPI_EC) += ec.o ec-y := driver.o device.o event.o poll.o intr.o -ec-y += setup.o +ec-y += setup.o ecdt.o ec-y += resource.o gpe.o addr.o read.o write.o ec-$(CONFIG_ACPI_DM_PROC) += proc.o diff --git a/drivers/acpi/drivers/ec/driver.c b/drivers/acpi/drivers/ec/driver.c index 4551bd3..f1f6fbc 100644 --- a/drivers/acpi/drivers/ec/driver.c +++ b/drivers/acpi/drivers/ec/driver.c @@ -60,6 +60,11 @@ static int ec_add(struct acpi_dev * ad) printk(KERN_INFO PREFIX "ec [%s] (gpe %ld)\n", acpi_dev_bid(ad), ae->e_gpe_bit); + /* + * Remove the early ECDT-enumerated EC device if it exists + */ + ec_ecdt_exit(ae); + return ret; } diff --git a/drivers/acpi/drivers/ec/ec.h b/drivers/acpi/drivers/ec/ec.h index 3ff7590..750cc6a 100644 --- a/drivers/acpi/drivers/ec/ec.h +++ b/drivers/acpi/drivers/ec/ec.h @@ -66,6 +66,11 @@ extern int ec_fake_ecdt; extern struct acpi_ec * first_ec; /* + * ecdt.c + */ +extern void ec_ecdt_exit(struct acpi_ec * ec); + +/* * device.c */ extern int ec_init(struct acpi_ec * ec); diff --git a/drivers/acpi/drivers/ec/ecdt.c b/drivers/acpi/drivers/ec/ecdt.c new file mode 100644 index 0000000..6ac6f75 --- /dev/null +++ b/drivers/acpi/drivers/ec/ecdt.c @@ -0,0 +1,157 @@ +/*** + * drivers/acpi/drivers/ec/ecdt.c - Early ECDT probing + * + * Copyright (C) 2006 Patrick Mochel + * + * Based on drivers/acpi/ec.c, which was + * + * Copyright (C) 2004 Luming Yu + * Copyright (C) 2001, 2002 Andy Grover + * Copyright (C) 2001, 2002 Paul Diefenbaugh + * + * This file is released under the GPLv2. + */ + +#include "ec.h" + +static struct acpi_ec * ec_ecdt; + + +static acpi_status fake_callback(acpi_handle handle, u32 level, + void * context, void ** retval) +{ + int ret; + + ret = ec_resource_init(ec_ecdt); + if (ret) + return AE_NOT_FOUND; + + ret = ec_init(ec_ecdt); + if (ret) + return AE_NOT_FOUND; + 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 setup_fake_ecdt(void) +{ + acpi_status status; + int ret = 0; + + printk(KERN_INFO PREFIX "Trying to make a fake ECDT\n"); + + status = acpi_get_devices(ACPI_EC_HID, fake_callback, NULL, NULL); + if (ACPI_FAILURE(status)) { + printk(KERN_INFO PREFIX "Could not make a fake ECDT\n"); + ret = -ENODEV; + } + return ret; +} + + +static int get_real_ecdt(void) +{ + acpi_status status; + struct acpi_table_ecdt * ecdt; + + status = acpi_get_firmware_table("ECDT", 1, ACPI_LOGICAL_ADDRESSING, + (struct acpi_table_header **) &ecdt); + if (ACPI_FAILURE(status)) + return -ENODEV; + + printk(KERN_INFO PREFIX "Found ECDT\n"); + + ec_ecdt->e_command = ecdt->ec_control; + ec_ecdt->e_status = ecdt->ec_control; + ec_ecdt->e_data = ecdt->ec_data; + ec_ecdt->e_gpe_bit = ecdt->gpe_bit; + + ec_ecdt->e_global_lock = 1; + ec_ecdt->e_uid = ecdt->uid; + + status = acpi_get_handle(NULL, ecdt->ec_id, &ec_ecdt->e_handle); + if (ACPI_FAILURE(status)) + return -ENODEV; + return 0; +} + +int __init acpi_ec_ecdt_probe(void) +{ + int ret; + + /* + * Generate a temporary EC to use until the namespace is scanned. + */ + ec_ecdt = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); + if (!ec_ecdt) + return -ENOMEM; + + /* + * Try to find a real ECDT + */ + ret = get_real_ecdt(); + + /* + * If it's not there, then try to create a fake one + */ + if (ret) { + if (ec_fake_ecdt) + ret = setup_fake_ecdt(); + } + + /* + * Still no love, return an error. + */ + if (ret) { + kfree(ec_ecdt); + ec_ecdt = NULL; + return 0; + } + + if (ec_poll_mode == EC_MODE_INTR) + ec_intr_init(ec_ecdt); + else + ec_poll_init(ec_ecdt); + + ret = ec_gpe_init(ec_ecdt); + if (ret) + goto Fail; + + ret = ec_addr_init(ec_ecdt); + if (ret) { + ec_gpe_exit(ec_ecdt); + goto Fail; + } + + return 0; + Fail: + err("Could not use ECDT"); + kfree(ec_ecdt); + ec_ecdt = NULL; + return -ENODEV; +} + + +void ec_ecdt_exit(struct acpi_ec * ec) +{ + unsigned long uid; + + acpi_evaluate_integer(&ec->e_handle, "_UID", NULL, &uid); + if (ec_ecdt && ec_ecdt->e_uid == uid) { + ec_addr_exit(ec_ecdt); + ec_gpe_exit(ec_ecdt); + kfree(ec_ecdt); + ec_ecdt = NULL; + } +} + --- 0.99.9.GIT