diff options
Diffstat (limited to 'drivers/acpi/ec.c')
-rw-r--r-- | drivers/acpi/ec.c | 208 |
1 files changed, 112 insertions, 96 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 9d66a47d32fb..48d4815603e5 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -186,14 +186,17 @@ static void advance_transaction(struct acpi_ec *ec); static void acpi_ec_event_handler(struct work_struct *work); static void acpi_ec_event_processor(struct work_struct *work); -struct acpi_ec *boot_ec, *first_ec; +struct acpi_ec *first_ec; EXPORT_SYMBOL(first_ec); + +static struct acpi_ec *boot_ec; static bool boot_ec_is_ecdt = false; static struct workqueue_struct *ec_query_wq; static int EC_FLAGS_QUERY_HANDSHAKE; /* Needs QR_EC issued when SCI_EVT set */ static int EC_FLAGS_CORRECT_ECDT; /* Needs ECDT port address correction */ static int EC_FLAGS_IGNORE_DSDT_GPE; /* Needs ECDT GPE as correction setting */ +static int EC_FLAGS_CLEAR_ON_RESUME; /* Needs acpi_ec_clear() on boot/resume */ /* -------------------------------------------------------------------------- * Logging/Debugging @@ -499,6 +502,26 @@ static inline void __acpi_ec_disable_event(struct acpi_ec *ec) ec_log_drv("event blocked"); } +/* + * Process _Q events that might have accumulated in the EC. + * Run with locked ec mutex. + */ +static void acpi_ec_clear(struct acpi_ec *ec) +{ + int i, status; + u8 value = 0; + + for (i = 0; i < ACPI_EC_CLEAR_MAX; i++) { + status = acpi_ec_query(ec, &value); + if (status || !value) + break; + } + if (unlikely(i == ACPI_EC_CLEAR_MAX)) + pr_warn("Warning: Maximum of %d stale EC events cleared\n", i); + else + pr_info("%d stale EC events cleared\n", i); +} + static void acpi_ec_enable_event(struct acpi_ec *ec) { unsigned long flags; @@ -507,6 +530,10 @@ static void acpi_ec_enable_event(struct acpi_ec *ec) if (acpi_ec_started(ec)) __acpi_ec_enable_event(ec); spin_unlock_irqrestore(&ec->lock, flags); + + /* Drain additional events if hardware requires that */ + if (EC_FLAGS_CLEAR_ON_RESUME) + acpi_ec_clear(ec); } #ifdef CONFIG_PM_SLEEP @@ -1539,49 +1566,6 @@ static int acpi_ec_setup(struct acpi_ec *ec, bool handle_events) return ret; } -static int acpi_config_boot_ec(struct acpi_ec *ec, acpi_handle handle, - bool handle_events, bool is_ecdt) -{ - int ret; - - /* - * Changing the ACPI handle results in a re-configuration of the - * boot EC. And if it happens after the namespace initialization, - * it causes _REG evaluations. - */ - if (boot_ec && boot_ec->handle != handle) - ec_remove_handlers(boot_ec); - - /* Unset old boot EC */ - if (boot_ec != ec) - acpi_ec_free(boot_ec); - - /* - * ECDT device creation is split into acpi_ec_ecdt_probe() and - * acpi_ec_ecdt_start(). This function takes care of completing the - * ECDT parsing logic as the handle update should be performed - * between the installation/uninstallation of the handlers. - */ - if (ec->handle != handle) - ec->handle = handle; - - ret = acpi_ec_setup(ec, handle_events); - if (ret) - return ret; - - /* Set new boot EC */ - if (!boot_ec) { - boot_ec = ec; - boot_ec_is_ecdt = is_ecdt; - } - - acpi_handle_info(boot_ec->handle, - "Used as boot %s EC to handle transactions%s\n", - is_ecdt ? "ECDT" : "DSDT", - handle_events ? " and events" : ""); - return ret; -} - static bool acpi_ec_ecdt_get_handle(acpi_handle *phandle) { struct acpi_table_ecdt *ecdt_ptr; @@ -1601,43 +1585,34 @@ static bool acpi_ec_ecdt_get_handle(acpi_handle *phandle) return true; } -static bool acpi_is_boot_ec(struct acpi_ec *ec) -{ - if (!boot_ec) - return false; - if (ec->command_addr == boot_ec->command_addr && - ec->data_addr == boot_ec->data_addr) - return true; - return false; -} - static int acpi_ec_add(struct acpi_device *device) { struct acpi_ec *ec = NULL; - int ret; - bool is_ecdt = false; + bool dep_update = true; acpi_status status; + int ret; strcpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_EC_CLASS); if (!strcmp(acpi_device_hid(device), ACPI_ECDT_HID)) { - is_ecdt = true; + boot_ec_is_ecdt = true; ec = boot_ec; + dep_update = false; } else { ec = acpi_ec_alloc(); if (!ec) return -ENOMEM; + status = ec_parse_device(device->handle, 0, ec, NULL); if (status != AE_CTRL_TERMINATE) { ret = -EINVAL; goto err_alloc; } - } - if (acpi_is_boot_ec(ec)) { - boot_ec_is_ecdt = is_ecdt; - if (!is_ecdt) { + if (boot_ec && ec->command_addr == boot_ec->command_addr && + ec->data_addr == boot_ec->data_addr) { + boot_ec_is_ecdt = false; /* * Trust PNP0C09 namespace location rather than * ECDT ID. But trust ECDT GPE rather than _GPE @@ -1649,12 +1624,17 @@ static int acpi_ec_add(struct acpi_device *device) acpi_ec_free(ec); ec = boot_ec; } - ret = acpi_config_boot_ec(ec, ec->handle, true, is_ecdt); - } else - ret = acpi_ec_setup(ec, true); + } + + ret = acpi_ec_setup(ec, true); if (ret) goto err_query; + if (ec == boot_ec) + acpi_handle_info(boot_ec->handle, + "Boot %s EC used to handle transactions and events\n", + boot_ec_is_ecdt ? "ECDT" : "DSDT"); + device->driver_data = ec; ret = !!request_region(ec->data_addr, 1, "EC data"); @@ -1662,7 +1642,7 @@ static int acpi_ec_add(struct acpi_device *device) ret = !!request_region(ec->command_addr, 1, "EC cmd"); WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr); - if (!is_ecdt) { + if (dep_update) { /* Reprobe devices depending on the EC */ acpi_walk_dep_device_list(ec->handle); } @@ -1730,10 +1710,10 @@ static const struct acpi_device_id ec_device_ids[] = { * namespace EC before the main ACPI device enumeration process. It is * retained for historical reason and will be deprecated in the future. */ -int __init acpi_ec_dsdt_probe(void) +void __init acpi_ec_dsdt_probe(void) { - acpi_status status; struct acpi_ec *ec; + acpi_status status; int ret; /* @@ -1743,21 +1723,22 @@ int __init acpi_ec_dsdt_probe(void) * picking up an invalid EC device. */ if (boot_ec) - return -ENODEV; + return; ec = acpi_ec_alloc(); if (!ec) - return -ENOMEM; + return; + /* * At this point, the namespace is initialized, so start to find * the namespace objects. */ - status = acpi_get_devices(ec_device_ids[0].id, - ec_parse_device, ec, NULL); + status = acpi_get_devices(ec_device_ids[0].id, ec_parse_device, ec, NULL); if (ACPI_FAILURE(status) || !ec->handle) { - ret = -ENODEV; - goto error; + acpi_ec_free(ec); + return; } + /* * When the DSDT EC is available, always re-configure boot EC to * have _REG evaluated. _REG can only be evaluated after the @@ -1765,11 +1746,16 @@ int __init acpi_ec_dsdt_probe(void) * At this point, the GPE is not fully initialized, so do not to * handle the events. */ - ret = acpi_config_boot_ec(ec, ec->handle, false, false); -error: - if (ret) + ret = acpi_ec_setup(ec, false); + if (ret) { acpi_ec_free(ec); - return ret; + return; + } + + boot_ec = ec; + + acpi_handle_info(ec->handle, + "Boot DSDT EC used to handle transactions\n"); } /* @@ -1821,6 +1807,31 @@ static int ec_flag_query_handshake(const struct dmi_system_id *id) #endif /* + * On some hardware it is necessary to clear events accumulated by the EC during + * sleep. These ECs stop reporting GPEs until they are manually polled, if too + * many events are accumulated. (e.g. Samsung Series 5/9 notebooks) + * + * https://bugzilla.kernel.org/show_bug.cgi?id=44161 + * + * Ideally, the EC should also be instructed NOT to accumulate events during + * sleep (which Windows seems to do somehow), but the interface to control this + * behaviour is not known at this time. + * + * Models known to be affected are Samsung 530Uxx/535Uxx/540Uxx/550Pxx/900Xxx, + * however it is very likely that other Samsung models are affected. + * + * On systems which don't accumulate _Q events during sleep, this extra check + * should be harmless. + */ +static int ec_clear_on_resume(const struct dmi_system_id *id) +{ + pr_debug("Detected system needing EC poll on resume.\n"); + EC_FLAGS_CLEAR_ON_RESUME = 1; + ec_event_clearing = ACPI_EC_EVT_TIMING_STATUS; + return 0; +} + +/* * Some ECDTs contain wrong register addresses. * MSI MS-171F * https://bugzilla.kernel.org/show_bug.cgi?id=12461 @@ -1869,39 +1880,38 @@ static const struct dmi_system_id ec_dmi_table[] __initconst = { ec_honor_ecdt_gpe, "ASUS X580VD", { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "X580VD"),}, NULL}, + { + ec_clear_on_resume, "Samsung hardware", { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD.")}, NULL}, {}, }; -int __init acpi_ec_ecdt_probe(void) +void __init acpi_ec_ecdt_probe(void) { - int ret; - acpi_status status; struct acpi_table_ecdt *ecdt_ptr; struct acpi_ec *ec; + acpi_status status; + int ret; - ec = acpi_ec_alloc(); - if (!ec) - return -ENOMEM; - /* - * Generate a boot ec context - */ + /* Generate a boot ec context. */ dmi_check_system(ec_dmi_table); status = acpi_get_table(ACPI_SIG_ECDT, 1, (struct acpi_table_header **)&ecdt_ptr); - if (ACPI_FAILURE(status)) { - ret = -ENODEV; - goto error; - } + if (ACPI_FAILURE(status)) + return; if (!ecdt_ptr->control.address || !ecdt_ptr->data.address) { /* * Asus X50GL: * https://bugzilla.kernel.org/show_bug.cgi?id=11880 */ - ret = -ENODEV; - goto error; + return; } + ec = acpi_ec_alloc(); + if (!ec) + return; + if (EC_FLAGS_CORRECT_ECDT) { ec->command_addr = ecdt_ptr->data.address; ec->data_addr = ecdt_ptr->control.address; @@ -1910,16 +1920,22 @@ int __init acpi_ec_ecdt_probe(void) ec->data_addr = ecdt_ptr->data.address; } ec->gpe = ecdt_ptr->gpe; + ec->handle = ACPI_ROOT_OBJECT; /* * At this point, the namespace is not initialized, so do not find * the namespace objects, or handle the events. */ - ret = acpi_config_boot_ec(ec, ACPI_ROOT_OBJECT, false, true); -error: - if (ret) + ret = acpi_ec_setup(ec, false); + if (ret) { acpi_ec_free(ec); - return ret; + return; + } + + boot_ec = ec; + boot_ec_is_ecdt = true; + + pr_info("Boot ECDT EC used to handle transactions\n"); } #ifdef CONFIG_PM_SLEEP |