Skip to content
ec.c 29.2 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
static acpi_status __init
acpi_fake_ecdt_callback(acpi_handle handle,
			u32 Level, void *context, void **retval)
Linus Torvalds's avatar
Linus Torvalds committed
{
	acpi_status status;
Linus Torvalds's avatar
Linus Torvalds committed

	init_MUTEX(&ec_ecdt->sem);
	if (acpi_ec_mode == EC_INTR) {
		init_waitqueue_head(&ec_ecdt->wait);
	}
Linus Torvalds's avatar
Linus Torvalds committed
	status = acpi_walk_resources(handle, METHOD_NAME__CRS,
				     acpi_ec_io_ports, ec_ecdt);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ACPI_FAILURE(status))
		return status;
	ec_ecdt->status_addr = ec_ecdt->command_addr;
Linus Torvalds's avatar
Linus Torvalds committed

	ec_ecdt->uid = -1;
	acpi_evaluate_integer(handle, "_UID", NULL, &ec_ecdt->uid);
Linus Torvalds's avatar
Linus Torvalds committed

	status =
	    acpi_evaluate_integer(handle, "_GPE", NULL,
Linus Torvalds's avatar
Linus Torvalds committed
	if (ACPI_FAILURE(status))
		return status;
	ec_ecdt->global_lock = TRUE;
	ec_ecdt->handle = handle;
Linus Torvalds's avatar
Linus Torvalds committed

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "GPE=0x%02x, ports=0x%2x, 0x%2x",
	       (u32) ec_ecdt->gpe_bit,
	       (u32) ec_ecdt->command_addr.address,
	       (u32) ec_ecdt->data_addr.address));
Linus Torvalds's avatar
Linus Torvalds committed

	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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	acpi_status status;
	int ret = 0;
Linus Torvalds's avatar
Linus Torvalds committed

	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Try to make an fake ECDT"));
Linus Torvalds's avatar
Linus Torvalds committed

	ec_ecdt = kmalloc(sizeof(struct acpi_ec), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!ec_ecdt) {
		ret = -ENOMEM;
		goto error;
	}
	memset(ec_ecdt, 0, sizeof(struct acpi_ec));
Linus Torvalds's avatar
Linus Torvalds committed

	status = acpi_get_devices(ACPI_EC_HID,
				  acpi_fake_ecdt_callback, NULL, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ACPI_FAILURE(status)) {
		kfree(ec_ecdt);
		ec_ecdt = NULL;
		ret = -ENODEV;
		ACPI_EXCEPTION((AE_INFO, status, "Can't make an fake ECDT"));
Linus Torvalds's avatar
Linus Torvalds committed
		goto error;
	}
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
	return ret;
}

static int __init acpi_ec_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;

	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);
	if (!ec_ecdt)
		return -ENOMEM;
	memset(ec_ecdt, 0, sizeof(struct acpi_ec));
	init_MUTEX(&ec_ecdt->sem);
	if (acpi_ec_mode == EC_INTR) {
		init_waitqueue_head(&ec_ecdt->wait);
	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;
Linus Torvalds's avatar
Linus Torvalds committed
	/* use the GL just to be safe */
	ec_ecdt->global_lock = TRUE;
	ec_ecdt->uid = ecdt_ptr->uid;
Linus Torvalds's avatar
Linus Torvalds committed

	status =
	    acpi_get_handle(NULL, ecdt_ptr->ec_id, &ec_ecdt->handle);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ACPI_FAILURE(status)) {
		goto error;
	}

	return 0;
  error:
	ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
Linus Torvalds's avatar
Linus Torvalds committed
	kfree(ec_ecdt);
	ec_ecdt = NULL;

	return -ENODEV;
}

static int __initdata acpi_fake_ecdt_enabled;
int __init acpi_ec_ecdt_probe(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	acpi_status status;
	int ret;
Linus Torvalds's avatar
Linus Torvalds committed

	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);
Linus Torvalds's avatar
Linus Torvalds committed
	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);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ACPI_FAILURE(status)) {
		acpi_remove_gpe_handler(NULL, ec_ecdt->gpe_bit,
					&acpi_ec_gpe_handler);
Linus Torvalds's avatar
Linus Torvalds committed
		goto error;
	}

	return 0;

      error:
	ACPI_EXCEPTION((AE_INFO, status, "Could not use ECDT"));
Linus Torvalds's avatar
Linus Torvalds committed
	kfree(ec_ecdt);
	ec_ecdt = NULL;

	return -ENODEV;
}

static int __init acpi_ec_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int result = 0;
Linus Torvalds's avatar
Linus Torvalds committed


	if (acpi_disabled)
Linus Torvalds's avatar
Linus Torvalds committed

	acpi_ec_dir = proc_mkdir(ACPI_EC_CLASS, acpi_root_dir);
	if (!acpi_ec_dir)
Linus Torvalds's avatar
Linus Torvalds committed

	/* 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);
Linus Torvalds's avatar
Linus Torvalds committed
}

subsys_initcall(acpi_ec_init);

/* EC driver currently not unloadable */
#if 0
static void __exit acpi_ec_exit(void)
Linus Torvalds's avatar
Linus Torvalds committed
{

	acpi_bus_unregister_driver(&acpi_ec_driver);

	remove_proc_entry(ACPI_EC_CLASS, acpi_root_dir);

Linus Torvalds's avatar
Linus Torvalds committed
}
#endif				/* 0 */
Linus Torvalds's avatar
Linus Torvalds committed

static int __init acpi_fake_ecdt_setup(char *str)
{
	acpi_fake_ecdt_enabled = 1;
Linus Torvalds's avatar
Linus Torvalds committed
}
Linus Torvalds's avatar
Linus Torvalds committed
__setup("acpi_fake_ecdt", acpi_fake_ecdt_setup);
static int __init acpi_ec_set_intr_mode(char *str)
	if (!get_option(&str, &intr))
	acpi_ec_driver.ops.add = acpi_ec_add;
	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "EC %s mode.\n", intr ? "interrupt" : "polling"));

__setup("ec_intr=", acpi_ec_set_intr_mode);