aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/acpi/ec.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/ec.c')
-rw-r--r--drivers/acpi/ec.c208
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