|
@@ -104,10 +104,12 @@ enum ec_command {
|
|
|
#define ACPI_EC_MAX_QUERIES 16 /* Maximum number of parallel queries */
|
|
|
|
|
|
enum {
|
|
|
+ EC_FLAGS_QUERY_ENABLED, /* Query is enabled */
|
|
|
EC_FLAGS_QUERY_PENDING, /* Query is pending */
|
|
|
EC_FLAGS_QUERY_GUARDING, /* Guard for SCI_EVT check */
|
|
|
EC_FLAGS_GPE_HANDLER_INSTALLED, /* GPE handler installed */
|
|
|
EC_FLAGS_EC_HANDLER_INSTALLED, /* OpReg handler installed */
|
|
|
+ EC_FLAGS_EVT_HANDLER_INSTALLED, /* _Qxx handlers installed */
|
|
|
EC_FLAGS_STARTED, /* Driver is started */
|
|
|
EC_FLAGS_STOPPED, /* Driver is stopped */
|
|
|
EC_FLAGS_COMMAND_STORM, /* GPE storms occurred to the
|
|
@@ -145,6 +147,10 @@ static unsigned int ec_storm_threshold __read_mostly = 8;
|
|
|
module_param(ec_storm_threshold, uint, 0644);
|
|
|
MODULE_PARM_DESC(ec_storm_threshold, "Maxim false GPE numbers not considered as GPE storm");
|
|
|
|
|
|
+static bool ec_freeze_events __read_mostly = true;
|
|
|
+module_param(ec_freeze_events, bool, 0644);
|
|
|
+MODULE_PARM_DESC(ec_freeze_events, "Disabling event handling during suspend/resume");
|
|
|
+
|
|
|
struct acpi_ec_query_handler {
|
|
|
struct list_head node;
|
|
|
acpi_ec_query_func func;
|
|
@@ -179,6 +185,7 @@ static void acpi_ec_event_processor(struct work_struct *work);
|
|
|
|
|
|
struct acpi_ec *boot_ec, *first_ec;
|
|
|
EXPORT_SYMBOL(first_ec);
|
|
|
+static bool boot_ec_is_ecdt = false;
|
|
|
static struct workqueue_struct *ec_query_wq;
|
|
|
|
|
|
static int EC_FLAGS_CLEAR_ON_RESUME; /* Needs acpi_ec_clear() on boot/resume */
|
|
@@ -239,6 +246,30 @@ static bool acpi_ec_started(struct acpi_ec *ec)
|
|
|
!test_bit(EC_FLAGS_STOPPED, &ec->flags);
|
|
|
}
|
|
|
|
|
|
+static bool acpi_ec_event_enabled(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * There is an OSPM early stage logic. During the early stages
|
|
|
+ * (boot/resume), OSPMs shouldn't enable the event handling, only
|
|
|
+ * the EC transactions are allowed to be performed.
|
|
|
+ */
|
|
|
+ if (!test_bit(EC_FLAGS_QUERY_ENABLED, &ec->flags))
|
|
|
+ return false;
|
|
|
+ /*
|
|
|
+ * However, disabling the event handling is experimental for late
|
|
|
+ * stage (suspend), and is controlled by the boot parameter of
|
|
|
+ * "ec_freeze_events":
|
|
|
+ * 1. true: The EC event handling is disabled before entering
|
|
|
+ * the noirq stage.
|
|
|
+ * 2. false: The EC event handling is automatically disabled as
|
|
|
+ * soon as the EC driver is stopped.
|
|
|
+ */
|
|
|
+ if (ec_freeze_events)
|
|
|
+ return acpi_ec_started(ec);
|
|
|
+ else
|
|
|
+ return test_bit(EC_FLAGS_STARTED, &ec->flags);
|
|
|
+}
|
|
|
+
|
|
|
static bool acpi_ec_flushed(struct acpi_ec *ec)
|
|
|
{
|
|
|
return ec->reference_count == 1;
|
|
@@ -429,7 +460,8 @@ static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec)
|
|
|
|
|
|
static void acpi_ec_submit_query(struct acpi_ec *ec)
|
|
|
{
|
|
|
- if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) {
|
|
|
+ if (acpi_ec_event_enabled(ec) &&
|
|
|
+ !test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) {
|
|
|
ec_dbg_evt("Command(%s) submitted/blocked",
|
|
|
acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY));
|
|
|
ec->nr_pending_queries++;
|
|
@@ -446,6 +478,86 @@ static void acpi_ec_complete_query(struct acpi_ec *ec)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static inline void __acpi_ec_enable_event(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ if (!test_and_set_bit(EC_FLAGS_QUERY_ENABLED, &ec->flags))
|
|
|
+ ec_log_drv("event unblocked");
|
|
|
+ if (!test_bit(EC_FLAGS_QUERY_PENDING, &ec->flags))
|
|
|
+ advance_transaction(ec);
|
|
|
+}
|
|
|
+
|
|
|
+static inline void __acpi_ec_disable_event(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ if (test_and_clear_bit(EC_FLAGS_QUERY_ENABLED, &ec->flags))
|
|
|
+ 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;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&ec->lock, flags);
|
|
|
+ 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);
|
|
|
+}
|
|
|
+
|
|
|
+static bool acpi_ec_query_flushed(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ bool flushed;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&ec->lock, flags);
|
|
|
+ flushed = !ec->nr_pending_queries;
|
|
|
+ spin_unlock_irqrestore(&ec->lock, flags);
|
|
|
+ return flushed;
|
|
|
+}
|
|
|
+
|
|
|
+static void __acpi_ec_flush_event(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * When ec_freeze_events is true, we need to flush events in
|
|
|
+ * the proper position before entering the noirq stage.
|
|
|
+ */
|
|
|
+ wait_event(ec->wait, acpi_ec_query_flushed(ec));
|
|
|
+ if (ec_query_wq)
|
|
|
+ flush_workqueue(ec_query_wq);
|
|
|
+}
|
|
|
+
|
|
|
+static void acpi_ec_disable_event(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&ec->lock, flags);
|
|
|
+ __acpi_ec_disable_event(ec);
|
|
|
+ spin_unlock_irqrestore(&ec->lock, flags);
|
|
|
+ __acpi_ec_flush_event(ec);
|
|
|
+}
|
|
|
+
|
|
|
static bool acpi_ec_guard_event(struct acpi_ec *ec)
|
|
|
{
|
|
|
bool guarded = true;
|
|
@@ -832,27 +944,6 @@ acpi_handle ec_get_handle(void)
|
|
|
}
|
|
|
EXPORT_SYMBOL(ec_get_handle);
|
|
|
|
|
|
-/*
|
|
|
- * 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_start(struct acpi_ec *ec, bool resuming)
|
|
|
{
|
|
|
unsigned long flags;
|
|
@@ -896,7 +987,8 @@ static void acpi_ec_stop(struct acpi_ec *ec, bool suspending)
|
|
|
if (!suspending) {
|
|
|
acpi_ec_complete_request(ec);
|
|
|
ec_dbg_ref(ec, "Decrease driver");
|
|
|
- }
|
|
|
+ } else if (!ec_freeze_events)
|
|
|
+ __acpi_ec_disable_event(ec);
|
|
|
clear_bit(EC_FLAGS_STARTED, &ec->flags);
|
|
|
clear_bit(EC_FLAGS_STOPPED, &ec->flags);
|
|
|
ec_log_drv("EC stopped");
|
|
@@ -918,20 +1010,6 @@ void acpi_ec_block_transactions(void)
|
|
|
}
|
|
|
|
|
|
void acpi_ec_unblock_transactions(void)
|
|
|
-{
|
|
|
- struct acpi_ec *ec = first_ec;
|
|
|
-
|
|
|
- if (!ec)
|
|
|
- return;
|
|
|
-
|
|
|
- /* Allow transactions to be carried out again */
|
|
|
- acpi_ec_start(ec, true);
|
|
|
-
|
|
|
- if (EC_FLAGS_CLEAR_ON_RESUME)
|
|
|
- acpi_ec_clear(ec);
|
|
|
-}
|
|
|
-
|
|
|
-void acpi_ec_unblock_transactions_early(void)
|
|
|
{
|
|
|
/*
|
|
|
* Allow transactions to happen again (this function is called from
|
|
@@ -1228,13 +1306,21 @@ acpi_ec_space_handler(u32 function, acpi_physical_address address,
|
|
|
static acpi_status
|
|
|
ec_parse_io_ports(struct acpi_resource *resource, void *context);
|
|
|
|
|
|
-static struct acpi_ec *make_acpi_ec(void)
|
|
|
+static void acpi_ec_free(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ if (first_ec == ec)
|
|
|
+ first_ec = NULL;
|
|
|
+ if (boot_ec == ec)
|
|
|
+ boot_ec = NULL;
|
|
|
+ kfree(ec);
|
|
|
+}
|
|
|
+
|
|
|
+static struct acpi_ec *acpi_ec_alloc(void)
|
|
|
{
|
|
|
struct acpi_ec *ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL);
|
|
|
|
|
|
if (!ec)
|
|
|
return NULL;
|
|
|
- ec->flags = 1 << EC_FLAGS_QUERY_PENDING;
|
|
|
mutex_init(&ec->mutex);
|
|
|
init_waitqueue_head(&ec->wait);
|
|
|
INIT_LIST_HEAD(&ec->list);
|
|
@@ -1290,7 +1376,12 @@ ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval)
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
}
|
|
|
|
|
|
-static int ec_install_handlers(struct acpi_ec *ec)
|
|
|
+/*
|
|
|
+ * Note: This function returns an error code only when the address space
|
|
|
+ * handler is not installed, which means "not able to handle
|
|
|
+ * transactions".
|
|
|
+ */
|
|
|
+static int ec_install_handlers(struct acpi_ec *ec, bool handle_events)
|
|
|
{
|
|
|
acpi_status status;
|
|
|
|
|
@@ -1319,6 +1410,16 @@ static int ec_install_handlers(struct acpi_ec *ec)
|
|
|
set_bit(EC_FLAGS_EC_HANDLER_INSTALLED, &ec->flags);
|
|
|
}
|
|
|
|
|
|
+ if (!handle_events)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!test_bit(EC_FLAGS_EVT_HANDLER_INSTALLED, &ec->flags)) {
|
|
|
+ /* Find and register all query methods */
|
|
|
+ acpi_walk_namespace(ACPI_TYPE_METHOD, ec->handle, 1,
|
|
|
+ acpi_ec_register_query_methods,
|
|
|
+ NULL, ec, NULL);
|
|
|
+ set_bit(EC_FLAGS_EVT_HANDLER_INSTALLED, &ec->flags);
|
|
|
+ }
|
|
|
if (!test_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags)) {
|
|
|
status = acpi_install_gpe_raw_handler(NULL, ec->gpe,
|
|
|
ACPI_GPE_EDGE_TRIGGERED,
|
|
@@ -1329,6 +1430,9 @@ static int ec_install_handlers(struct acpi_ec *ec)
|
|
|
if (test_bit(EC_FLAGS_STARTED, &ec->flags) &&
|
|
|
ec->reference_count >= 1)
|
|
|
acpi_ec_enable_gpe(ec, true);
|
|
|
+
|
|
|
+ /* EC is fully operational, allow queries */
|
|
|
+ acpi_ec_enable_event(ec);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1363,23 +1467,104 @@ static void ec_remove_handlers(struct acpi_ec *ec)
|
|
|
pr_err("failed to remove gpe handler\n");
|
|
|
clear_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags);
|
|
|
}
|
|
|
+ if (test_bit(EC_FLAGS_EVT_HANDLER_INSTALLED, &ec->flags)) {
|
|
|
+ acpi_ec_remove_query_handlers(ec, true, 0);
|
|
|
+ clear_bit(EC_FLAGS_EVT_HANDLER_INSTALLED, &ec->flags);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-static struct acpi_ec *acpi_ec_alloc(void)
|
|
|
+static int acpi_ec_setup(struct acpi_ec *ec, bool handle_events)
|
|
|
{
|
|
|
- struct acpi_ec *ec;
|
|
|
+ int ret;
|
|
|
|
|
|
- /* Check for boot EC */
|
|
|
- if (boot_ec) {
|
|
|
- ec = boot_ec;
|
|
|
- boot_ec = NULL;
|
|
|
- ec_remove_handlers(ec);
|
|
|
- if (first_ec == ec)
|
|
|
- first_ec = NULL;
|
|
|
- } else {
|
|
|
- ec = make_acpi_ec();
|
|
|
+ ret = ec_install_handlers(ec, handle_events);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ /* First EC capable of handling transactions */
|
|
|
+ if (!first_ec) {
|
|
|
+ first_ec = ec;
|
|
|
+ acpi_handle_info(first_ec->handle, "Used as first EC\n");
|
|
|
}
|
|
|
- return ec;
|
|
|
+
|
|
|
+ acpi_handle_info(ec->handle,
|
|
|
+ "GPE=0x%lx, EC_CMD/EC_SC=0x%lx, EC_DATA=0x%lx\n",
|
|
|
+ ec->gpe, ec->command_addr, ec->data_addr);
|
|
|
+ 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;
|
|
|
+ acpi_status status;
|
|
|
+ acpi_handle handle;
|
|
|
+
|
|
|
+ status = acpi_get_table(ACPI_SIG_ECDT, 1,
|
|
|
+ (struct acpi_table_header **)&ecdt_ptr);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ status = acpi_get_handle(NULL, ecdt_ptr->id, &handle);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ *phandle = handle;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static bool acpi_is_boot_ec(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ if (!boot_ec)
|
|
|
+ return false;
|
|
|
+ if (ec->handle == boot_ec->handle &&
|
|
|
+ ec->gpe == boot_ec->gpe &&
|
|
|
+ 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)
|
|
@@ -1395,16 +1580,21 @@ static int acpi_ec_add(struct acpi_device *device)
|
|
|
return -ENOMEM;
|
|
|
if (ec_parse_device(device->handle, 0, ec, NULL) !=
|
|
|
AE_CTRL_TERMINATE) {
|
|
|
- kfree(ec);
|
|
|
- return -EINVAL;
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err_alloc;
|
|
|
}
|
|
|
|
|
|
- /* Find and register all query methods */
|
|
|
- acpi_walk_namespace(ACPI_TYPE_METHOD, ec->handle, 1,
|
|
|
- acpi_ec_register_query_methods, NULL, ec, NULL);
|
|
|
+ if (acpi_is_boot_ec(ec)) {
|
|
|
+ boot_ec_is_ecdt = false;
|
|
|
+ acpi_handle_debug(ec->handle, "duplicated.\n");
|
|
|
+ acpi_ec_free(ec);
|
|
|
+ ec = boot_ec;
|
|
|
+ ret = acpi_config_boot_ec(ec, ec->handle, true, false);
|
|
|
+ } else
|
|
|
+ ret = acpi_ec_setup(ec, true);
|
|
|
+ if (ret)
|
|
|
+ goto err_query;
|
|
|
|
|
|
- if (!first_ec)
|
|
|
- first_ec = ec;
|
|
|
device->driver_data = ec;
|
|
|
|
|
|
ret = !!request_region(ec->data_addr, 1, "EC data");
|
|
@@ -1412,20 +1602,17 @@ 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);
|
|
|
|
|
|
- pr_info("GPE = 0x%lx, I/O: command/status = 0x%lx, data = 0x%lx\n",
|
|
|
- ec->gpe, ec->command_addr, ec->data_addr);
|
|
|
-
|
|
|
- ret = ec_install_handlers(ec);
|
|
|
-
|
|
|
/* Reprobe devices depending on the EC */
|
|
|
acpi_walk_dep_device_list(ec->handle);
|
|
|
+ acpi_handle_debug(ec->handle, "enumerated.\n");
|
|
|
+ return 0;
|
|
|
|
|
|
- /* EC is fully operational, allow queries */
|
|
|
- clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags);
|
|
|
-
|
|
|
- /* Clear stale _Q events if hardware might require that */
|
|
|
- if (EC_FLAGS_CLEAR_ON_RESUME)
|
|
|
- acpi_ec_clear(ec);
|
|
|
+err_query:
|
|
|
+ if (ec != boot_ec)
|
|
|
+ acpi_ec_remove_query_handlers(ec, true, 0);
|
|
|
+err_alloc:
|
|
|
+ if (ec != boot_ec)
|
|
|
+ acpi_ec_free(ec);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -1437,14 +1624,13 @@ static int acpi_ec_remove(struct acpi_device *device)
|
|
|
return -EINVAL;
|
|
|
|
|
|
ec = acpi_driver_data(device);
|
|
|
- ec_remove_handlers(ec);
|
|
|
- acpi_ec_remove_query_handlers(ec, true, 0);
|
|
|
release_region(ec->data_addr, 1);
|
|
|
release_region(ec->command_addr, 1);
|
|
|
device->driver_data = NULL;
|
|
|
- if (ec == first_ec)
|
|
|
- first_ec = NULL;
|
|
|
- kfree(ec);
|
|
|
+ if (ec != boot_ec) {
|
|
|
+ ec_remove_handlers(ec);
|
|
|
+ acpi_ec_free(ec);
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1486,9 +1672,8 @@ int __init acpi_ec_dsdt_probe(void)
|
|
|
if (!ec)
|
|
|
return -ENOMEM;
|
|
|
/*
|
|
|
- * Finding EC from DSDT if there is no ECDT EC available. When this
|
|
|
- * function is invoked, ACPI tables have been fully loaded, we can
|
|
|
- * walk namespace now.
|
|
|
+ * 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);
|
|
@@ -1496,16 +1681,47 @@ int __init acpi_ec_dsdt_probe(void)
|
|
|
ret = -ENODEV;
|
|
|
goto error;
|
|
|
}
|
|
|
- ret = ec_install_handlers(ec);
|
|
|
-
|
|
|
+ /*
|
|
|
+ * When the DSDT EC is available, always re-configure boot EC to
|
|
|
+ * have _REG evaluated. _REG can only be evaluated after the
|
|
|
+ * namespace initialization.
|
|
|
+ * 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)
|
|
|
- kfree(ec);
|
|
|
- else
|
|
|
- first_ec = boot_ec = ec;
|
|
|
+ acpi_ec_free(ec);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * If the DSDT EC is not functioning, we still need to prepare a fully
|
|
|
+ * functioning ECDT EC first in order to handle the events.
|
|
|
+ * https://bugzilla.kernel.org/show_bug.cgi?id=115021
|
|
|
+ */
|
|
|
+int __init acpi_ec_ecdt_start(void)
|
|
|
+{
|
|
|
+ acpi_handle handle;
|
|
|
+
|
|
|
+ if (!boot_ec)
|
|
|
+ return -ENODEV;
|
|
|
+ /*
|
|
|
+ * The DSDT EC should have already been started in
|
|
|
+ * acpi_ec_add().
|
|
|
+ */
|
|
|
+ if (!boot_ec_is_ecdt)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * At this point, the namespace and the GPE is initialized, so
|
|
|
+ * start to find the namespace objects and handle the events.
|
|
|
+ */
|
|
|
+ if (!acpi_ec_ecdt_get_handle(&handle))
|
|
|
+ return -ENODEV;
|
|
|
+ return acpi_config_boot_ec(boot_ec, handle, true, true);
|
|
|
+}
|
|
|
+
|
|
|
#if 0
|
|
|
/*
|
|
|
* Some EC firmware variations refuses to respond QR_EC when SCI_EVT is not
|
|
@@ -1600,7 +1816,6 @@ int __init acpi_ec_ecdt_probe(void)
|
|
|
goto error;
|
|
|
}
|
|
|
|
|
|
- pr_info("EC description table is found, configuring boot EC\n");
|
|
|
if (EC_FLAGS_CORRECT_ECDT) {
|
|
|
ec->command_addr = ecdt_ptr->data.address;
|
|
|
ec->data_addr = ecdt_ptr->control.address;
|
|
@@ -1609,16 +1824,90 @@ int __init acpi_ec_ecdt_probe(void)
|
|
|
ec->data_addr = ecdt_ptr->data.address;
|
|
|
}
|
|
|
ec->gpe = ecdt_ptr->gpe;
|
|
|
- ec->handle = ACPI_ROOT_OBJECT;
|
|
|
- ret = ec_install_handlers(ec);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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)
|
|
|
- kfree(ec);
|
|
|
- else
|
|
|
- first_ec = boot_ec = ec;
|
|
|
+ acpi_ec_free(ec);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
|
+static void acpi_ec_enter_noirq(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ if (ec == first_ec) {
|
|
|
+ spin_lock_irqsave(&ec->lock, flags);
|
|
|
+ ec->saved_busy_polling = ec_busy_polling;
|
|
|
+ ec->saved_polling_guard = ec_polling_guard;
|
|
|
+ ec_busy_polling = true;
|
|
|
+ ec_polling_guard = 0;
|
|
|
+ ec_log_drv("interrupt blocked");
|
|
|
+ spin_unlock_irqrestore(&ec->lock, flags);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void acpi_ec_leave_noirq(struct acpi_ec *ec)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ if (ec == first_ec) {
|
|
|
+ spin_lock_irqsave(&ec->lock, flags);
|
|
|
+ ec_busy_polling = ec->saved_busy_polling;
|
|
|
+ ec_polling_guard = ec->saved_polling_guard;
|
|
|
+ ec_log_drv("interrupt unblocked");
|
|
|
+ spin_unlock_irqrestore(&ec->lock, flags);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int acpi_ec_suspend_noirq(struct device *dev)
|
|
|
+{
|
|
|
+ struct acpi_ec *ec =
|
|
|
+ acpi_driver_data(to_acpi_device(dev));
|
|
|
+
|
|
|
+ acpi_ec_enter_noirq(ec);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int acpi_ec_resume_noirq(struct device *dev)
|
|
|
+{
|
|
|
+ struct acpi_ec *ec =
|
|
|
+ acpi_driver_data(to_acpi_device(dev));
|
|
|
+
|
|
|
+ acpi_ec_leave_noirq(ec);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int acpi_ec_suspend(struct device *dev)
|
|
|
+{
|
|
|
+ struct acpi_ec *ec =
|
|
|
+ acpi_driver_data(to_acpi_device(dev));
|
|
|
+
|
|
|
+ if (ec_freeze_events)
|
|
|
+ acpi_ec_disable_event(ec);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int acpi_ec_resume(struct device *dev)
|
|
|
+{
|
|
|
+ struct acpi_ec *ec =
|
|
|
+ acpi_driver_data(to_acpi_device(dev));
|
|
|
+
|
|
|
+ acpi_ec_enable_event(ec);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+static const struct dev_pm_ops acpi_ec_pm = {
|
|
|
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(acpi_ec_suspend_noirq, acpi_ec_resume_noirq)
|
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(acpi_ec_suspend, acpi_ec_resume)
|
|
|
+};
|
|
|
+
|
|
|
static int param_set_event_clearing(const char *val, struct kernel_param *kp)
|
|
|
{
|
|
|
int result = 0;
|
|
@@ -1664,6 +1953,7 @@ static struct acpi_driver acpi_ec_driver = {
|
|
|
.add = acpi_ec_add,
|
|
|
.remove = acpi_ec_remove,
|
|
|
},
|
|
|
+ .drv.pm = &acpi_ec_pm,
|
|
|
};
|
|
|
|
|
|
static inline int acpi_ec_query_init(void)
|