diff options
-rw-r--r-- | drivers/acpi/ec.c | 68 | ||||
-rw-r--r-- | drivers/acpi/internal.h | 1 |
2 files changed, 66 insertions, 3 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index a6179b71c573..1fa14634c3f6 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -145,6 +145,11 @@ static bool acpi_ec_started(struct acpi_ec *ec) !test_bit(EC_FLAGS_STOPPED, &ec->flags); } +static bool acpi_ec_flushed(struct acpi_ec *ec) +{ + return ec->reference_count == 1; +} + /* -------------------------------------------------------------------------- * EC Registers * -------------------------------------------------------------------------- */ @@ -266,6 +271,44 @@ static inline void acpi_ec_clear_gpe(struct acpi_ec *ec) * Transaction Management * -------------------------------------------------------------------------- */ +static void acpi_ec_submit_request(struct acpi_ec *ec) +{ + ec->reference_count++; + if (ec->reference_count == 1) + acpi_ec_enable_gpe(ec, true); +} + +static void acpi_ec_complete_request(struct acpi_ec *ec) +{ + bool flushed = false; + + ec->reference_count--; + if (ec->reference_count == 0) + acpi_ec_disable_gpe(ec, true); + flushed = acpi_ec_flushed(ec); + if (flushed) + wake_up(&ec->wait); +} + +/* + * acpi_ec_submit_flushable_request() - Increase the reference count unless + * the flush operation is not in + * progress + * @ec: the EC device + * + * This function must be used before taking a new action that should hold + * the reference count. If this function returns false, then the action + * must be discarded or it will prevent the flush operation from being + * completed. + */ +static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec) +{ + if (!acpi_ec_started(ec)) + return false; + acpi_ec_submit_request(ec); + return true; +} + static void acpi_ec_submit_query(struct acpi_ec *ec) { if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) { @@ -426,7 +469,8 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, udelay(ACPI_EC_MSI_UDELAY); /* start transaction */ spin_lock_irqsave(&ec->lock, tmp); - if (!acpi_ec_started(ec)) { + /* Enable GPE for command processing (IBF=0/OBF=1) */ + if (!acpi_ec_submit_flushable_request(ec)) { ret = -EINVAL; goto unlock; } @@ -441,6 +485,8 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, pr_debug("***** Command(%s) stopped *****\n", acpi_ec_cmd_string(t->command)); ec->curr = NULL; + /* Disable GPE for command processing (IBF=0/OBF=1) */ + acpi_ec_complete_request(ec); unlock: spin_unlock_irqrestore(&ec->lock, tmp); return ret; @@ -614,13 +660,25 @@ static void acpi_ec_start(struct acpi_ec *ec, bool resuming) spin_lock_irqsave(&ec->lock, flags); if (!test_and_set_bit(EC_FLAGS_STARTED, &ec->flags)) { pr_debug("+++++ Starting EC +++++\n"); + /* Enable GPE for event processing (SCI_EVT=1) */ if (!resuming) - acpi_ec_enable_gpe(ec, true); + acpi_ec_submit_request(ec); pr_info("+++++ EC started +++++\n"); } spin_unlock_irqrestore(&ec->lock, flags); } +static bool acpi_ec_stopped(struct acpi_ec *ec) +{ + unsigned long flags; + bool flushed; + + spin_lock_irqsave(&ec->lock, flags); + flushed = acpi_ec_flushed(ec); + spin_unlock_irqrestore(&ec->lock, flags); + return flushed; +} + static void acpi_ec_stop(struct acpi_ec *ec, bool suspending) { unsigned long flags; @@ -629,8 +687,12 @@ static void acpi_ec_stop(struct acpi_ec *ec, bool suspending) if (acpi_ec_started(ec)) { pr_debug("+++++ Stopping EC +++++\n"); set_bit(EC_FLAGS_STOPPED, &ec->flags); + spin_unlock_irqrestore(&ec->lock, flags); + wait_event(ec->wait, acpi_ec_stopped(ec)); + spin_lock_irqsave(&ec->lock, flags); + /* Disable GPE for event processing (SCI_EVT=1) */ if (!suspending) - acpi_ec_disable_gpe(ec, true); + acpi_ec_complete_request(ec); clear_bit(EC_FLAGS_STARTED, &ec->flags); clear_bit(EC_FLAGS_STOPPED, &ec->flags); pr_info("+++++ EC stopped +++++\n"); diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index dc420787ffcd..7dc69d82f658 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -122,6 +122,7 @@ struct acpi_ec { unsigned long data_addr; unsigned long global_lock; unsigned long flags; + unsigned long reference_count; struct mutex mutex; wait_queue_head_t wait; struct list_head list; |