summary refs log tree commit diff
path: root/drivers/acpi/ec.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/ec.c')
-rw-r--r--drivers/acpi/ec.c81
1 files changed, 73 insertions, 8 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index c385606bbceb..2540870e89f7 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -121,6 +121,7 @@ struct transaction {
 };
 
 static int acpi_ec_query(struct acpi_ec *ec, u8 *data);
+static void advance_transaction(struct acpi_ec *ec);
 
 struct acpi_ec *boot_ec, *first_ec;
 EXPORT_SYMBOL(first_ec);
@@ -132,7 +133,7 @@ static int EC_FLAGS_CLEAR_ON_RESUME; /* Needs acpi_ec_clear() on boot/resume */
 static int EC_FLAGS_QUERY_HANDSHAKE; /* Needs QR_EC issued when SCI_EVT set */
 
 /* --------------------------------------------------------------------------
- *                           Transaction Management
+ *                           EC Registers
  * -------------------------------------------------------------------------- */
 
 static inline u8 acpi_ec_read_status(struct acpi_ec *ec)
@@ -191,6 +192,64 @@ static const char *acpi_ec_cmd_string(u8 cmd)
 #define acpi_ec_cmd_string(cmd)		"UNDEF"
 #endif
 
+/* --------------------------------------------------------------------------
+ *                           GPE Registers
+ * -------------------------------------------------------------------------- */
+
+static inline bool acpi_ec_is_gpe_raised(struct acpi_ec *ec)
+{
+	acpi_event_status gpe_status = 0;
+
+	(void)acpi_get_gpe_status(NULL, ec->gpe, &gpe_status);
+	return (gpe_status & ACPI_EVENT_FLAG_SET) ? true : false;
+}
+
+static inline void acpi_ec_enable_gpe(struct acpi_ec *ec, bool open)
+{
+	if (open)
+		acpi_enable_gpe(NULL, ec->gpe);
+	else
+		acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_ENABLE);
+	if (acpi_ec_is_gpe_raised(ec)) {
+		/*
+		 * On some platforms, EN=1 writes cannot trigger GPE. So
+		 * software need to manually trigger a pseudo GPE event on
+		 * EN=1 writes.
+		 */
+		pr_debug("***** Polling quirk *****\n");
+		advance_transaction(ec);
+	}
+}
+
+static inline void acpi_ec_disable_gpe(struct acpi_ec *ec, bool close)
+{
+	if (close)
+		acpi_disable_gpe(NULL, ec->gpe);
+	else
+		acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);
+}
+
+static inline void acpi_ec_clear_gpe(struct acpi_ec *ec)
+{
+	/*
+	 * GPE STS is a W1C register, which means:
+	 * 1. Software can clear it without worrying about clearing other
+	 *    GPEs' STS bits when the hardware sets them in parallel.
+	 * 2. As long as software can ensure only clearing it when it is
+	 *    set, hardware won't set it in parallel.
+	 * So software can clear GPE in any contexts.
+	 * Warning: do not move the check into advance_transaction() as the
+	 * EC commands will be sent without GPE raised.
+	 */
+	if (!acpi_ec_is_gpe_raised(ec))
+		return;
+	acpi_clear_gpe(NULL, ec->gpe);
+}
+
+/* --------------------------------------------------------------------------
+ *                           Transaction Management
+ * -------------------------------------------------------------------------- */
+
 static void acpi_ec_submit_query(struct acpi_ec *ec)
 {
 	if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) {
@@ -227,6 +286,12 @@ static void advance_transaction(struct acpi_ec *ec)
 
 	pr_debug("===== %s (%d) =====\n",
 		 in_interrupt() ? "IRQ" : "TASK", smp_processor_id());
+	/*
+	 * By always clearing STS before handling all indications, we can
+	 * ensure a hardware STS 0->1 change after this clearing can always
+	 * trigger a GPE interrupt.
+	 */
+	acpi_ec_clear_gpe(ec);
 	status = acpi_ec_read_status(ec);
 	t = ec->curr;
 	if (!t)
@@ -378,7 +443,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t)
 	/* disable GPE during transaction if storm is detected */
 	if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
 		/* It has to be disabled, so that it doesn't trigger. */
-		acpi_disable_gpe(NULL, ec->gpe);
+		acpi_ec_disable_gpe(ec, false);
 	}
 
 	status = acpi_ec_transaction_unlocked(ec, t);
@@ -386,7 +451,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t)
 	if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) {
 		msleep(1);
 		/* It is safe to enable the GPE outside of the transaction. */
-		acpi_enable_gpe(NULL, ec->gpe);
+		acpi_ec_enable_gpe(ec, false);
 	} else if (t->irq_count > ec_storm_threshold) {
 		pr_info("GPE storm detected(%d GPEs), "
 			"transactions will use polling mode\n",
@@ -693,7 +758,7 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device,
 	spin_lock_irqsave(&ec->lock, flags);
 	advance_transaction(ec);
 	spin_unlock_irqrestore(&ec->lock, flags);
-	return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
+	return ACPI_INTERRUPT_HANDLED;
 }
 
 /* --------------------------------------------------------------------------
@@ -812,13 +877,13 @@ static int ec_install_handlers(struct acpi_ec *ec)
 
 	if (test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
 		return 0;
-	status = acpi_install_gpe_handler(NULL, ec->gpe,
+	status = acpi_install_gpe_raw_handler(NULL, ec->gpe,
 				  ACPI_GPE_EDGE_TRIGGERED,
 				  &acpi_ec_gpe_handler, ec);
 	if (ACPI_FAILURE(status))
 		return -ENODEV;
 
-	acpi_enable_gpe(NULL, ec->gpe);
+	acpi_ec_enable_gpe(ec, true);
 	status = acpi_install_address_space_handler(ec->handle,
 						    ACPI_ADR_SPACE_EC,
 						    &acpi_ec_space_handler,
@@ -833,7 +898,7 @@ static int ec_install_handlers(struct acpi_ec *ec)
 			pr_err("Fail in evaluating the _REG object"
 				" of EC device. Broken bios is suspected.\n");
 		} else {
-			acpi_disable_gpe(NULL, ec->gpe);
+			acpi_ec_disable_gpe(ec, true);
 			acpi_remove_gpe_handler(NULL, ec->gpe,
 				&acpi_ec_gpe_handler);
 			return -ENODEV;
@@ -848,7 +913,7 @@ static void ec_remove_handlers(struct acpi_ec *ec)
 {
 	if (!test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
 		return;
-	acpi_disable_gpe(NULL, ec->gpe);
+	acpi_ec_disable_gpe(ec, true);
 	if (ACPI_FAILURE(acpi_remove_address_space_handler(ec->handle,
 				ACPI_ADR_SPACE_EC, &acpi_ec_space_handler)))
 		pr_err("failed to remove space handler\n");