ACPI / EC: Fix a boot EC regresion by restoring boot EC support for the DSDT EC
[cascardo/linux.git] / drivers / acpi / ec.c
index b420fb4..73c76d6 100644 (file)
@@ -105,8 +105,8 @@ enum ec_command {
 enum {
        EC_FLAGS_QUERY_PENDING,         /* Query is pending */
        EC_FLAGS_QUERY_GUARDING,        /* Guard for SCI_EVT check */
-       EC_FLAGS_HANDLERS_INSTALLED,    /* Handlers for GPE and
-                                        * OpReg are installed */
+       EC_FLAGS_GPE_HANDLER_INSTALLED, /* GPE handler installed */
+       EC_FLAGS_EC_HANDLER_INSTALLED,  /* OpReg handler installed */
        EC_FLAGS_STARTED,               /* Driver is started */
        EC_FLAGS_STOPPED,               /* Driver is stopped */
        EC_FLAGS_COMMAND_STORM,         /* GPE storms occurred to the
@@ -175,10 +175,9 @@ static void acpi_ec_event_processor(struct work_struct *work);
 struct acpi_ec *boot_ec, *first_ec;
 EXPORT_SYMBOL(first_ec);
 
-static int EC_FLAGS_VALIDATE_ECDT; /* ASUStec ECDTs need to be validated */
-static int EC_FLAGS_SKIP_DSDT_SCAN; /* Not all BIOS survive early DSDT scan */
 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 */
+static int EC_FLAGS_CORRECT_ECDT; /* Needs ECDT port address correction */
 
 /* --------------------------------------------------------------------------
  *                           Logging/Debugging
@@ -367,7 +366,8 @@ static inline void acpi_ec_clear_gpe(struct acpi_ec *ec)
 static void acpi_ec_submit_request(struct acpi_ec *ec)
 {
        ec->reference_count++;
-       if (ec->reference_count == 1)
+       if (test_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags) &&
+           ec->reference_count == 1)
                acpi_ec_enable_gpe(ec, true);
 }
 
@@ -376,7 +376,8 @@ static void acpi_ec_complete_request(struct acpi_ec *ec)
        bool flushed = false;
 
        ec->reference_count--;
-       if (ec->reference_count == 0)
+       if (test_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags) &&
+           ec->reference_count == 0)
                acpi_ec_disable_gpe(ec, true);
        flushed = acpi_ec_flushed(ec);
        if (flushed)
@@ -1287,52 +1288,64 @@ static int ec_install_handlers(struct acpi_ec *ec)
 {
        acpi_status status;
 
-       if (test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
-               return 0;
-       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_ec_start(ec, false);
-       status = acpi_install_address_space_handler(ec->handle,
-                                                   ACPI_ADR_SPACE_EC,
-                                                   &acpi_ec_space_handler,
-                                                   NULL, ec);
-       if (ACPI_FAILURE(status)) {
-               if (status == AE_NOT_FOUND) {
-                       /*
-                        * Maybe OS fails in evaluating the _REG object.
-                        * The AE_NOT_FOUND error will be ignored and OS
-                        * continue to initialize EC.
-                        */
-                       pr_err("Fail in evaluating the _REG object"
-                               " of EC device. Broken bios is suspected.\n");
-               } else {
-                       acpi_ec_stop(ec, false);
-                       acpi_remove_gpe_handler(NULL, ec->gpe,
-                               &acpi_ec_gpe_handler);
-                       return -ENODEV;
+
+       if (!test_bit(EC_FLAGS_EC_HANDLER_INSTALLED, &ec->flags)) {
+               status = acpi_install_address_space_handler(ec->handle,
+                                                           ACPI_ADR_SPACE_EC,
+                                                           &acpi_ec_space_handler,
+                                                           NULL, ec);
+               if (ACPI_FAILURE(status)) {
+                       if (status == AE_NOT_FOUND) {
+                               /*
+                                * Maybe OS fails in evaluating the _REG
+                                * object. The AE_NOT_FOUND error will be
+                                * ignored and OS * continue to initialize
+                                * EC.
+                                */
+                               pr_err("Fail in evaluating the _REG object"
+                                       " of EC device. Broken bios is suspected.\n");
+                       } else {
+                               acpi_ec_stop(ec, false);
+                               return -ENODEV;
+                       }
+               }
+               set_bit(EC_FLAGS_EC_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,
+                                         &acpi_ec_gpe_handler, ec);
+               /* This is not fatal as we can poll EC events */
+               if (ACPI_SUCCESS(status)) {
+                       set_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags);
+                       if (test_bit(EC_FLAGS_STARTED, &ec->flags) &&
+                           ec->reference_count >= 1)
+                               acpi_ec_enable_gpe(ec, true);
                }
        }
 
-       set_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags);
        return 0;
 }
 
 static void ec_remove_handlers(struct acpi_ec *ec)
 {
-       if (!test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags))
-               return;
        acpi_ec_stop(ec, false);
-       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");
-       if (ACPI_FAILURE(acpi_remove_gpe_handler(NULL, ec->gpe,
-                               &acpi_ec_gpe_handler)))
-               pr_err("failed to remove gpe handler\n");
-       clear_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags);
+
+       if (test_bit(EC_FLAGS_EC_HANDLER_INSTALLED, &ec->flags)) {
+               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");
+               clear_bit(EC_FLAGS_EC_HANDLER_INSTALLED, &ec->flags);
+       }
+
+       if (test_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags)) {
+               if (ACPI_FAILURE(acpi_remove_gpe_handler(NULL, ec->gpe,
+                                       &acpi_ec_gpe_handler)))
+                       pr_err("failed to remove gpe handler\n");
+               clear_bit(EC_FLAGS_GPE_HANDLER_INSTALLED, &ec->flags);
+       }
 }
 
 static int acpi_ec_add(struct acpi_device *device)
@@ -1344,11 +1357,12 @@ static int acpi_ec_add(struct acpi_device *device)
        strcpy(acpi_device_class(device), ACPI_EC_CLASS);
 
        /* Check for boot EC */
-       if (boot_ec &&
-           (boot_ec->handle == device->handle ||
-            boot_ec->handle == ACPI_ROOT_OBJECT)) {
+       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();
                if (!ec)
@@ -1432,34 +1446,35 @@ ec_parse_io_ports(struct acpi_resource *resource, void *context)
        return AE_OK;
 }
 
-int __init acpi_boot_ec_enable(void)
-{
-       if (!boot_ec || test_bit(EC_FLAGS_HANDLERS_INSTALLED, &boot_ec->flags))
-               return 0;
-       if (!ec_install_handlers(boot_ec)) {
-               first_ec = boot_ec;
-               return 0;
-       }
-       return -EFAULT;
-}
-
 static const struct acpi_device_id ec_device_ids[] = {
        {"PNP0C09", 0},
        {"", 0},
 };
 
-/* Some BIOS do not survive early DSDT scan, skip it */
-static int ec_skip_dsdt_scan(const struct dmi_system_id *id)
+int __init acpi_ec_dsdt_probe(void)
 {
-       EC_FLAGS_SKIP_DSDT_SCAN = 1;
-       return 0;
-}
+       acpi_status status;
 
-/* ASUStek often supplies us with broken ECDT, validate it */
-static int ec_validate_ecdt(const struct dmi_system_id *id)
-{
-       EC_FLAGS_VALIDATE_ECDT = 1;
-       return 0;
+       if (boot_ec)
+               return 0;
+
+       /*
+        * 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.
+        */
+       boot_ec = make_acpi_ec();
+       if (!boot_ec)
+               return -ENOMEM;
+       status = acpi_get_devices(ec_device_ids[0].id,
+                                 ec_parse_device, boot_ec, NULL);
+       if (ACPI_FAILURE(status) || !boot_ec->handle)
+               return -ENODEV;
+       if (!ec_install_handlers(boot_ec)) {
+               first_ec = boot_ec;
+               return 0;
+       }
+       return -EFAULT;
 }
 
 #if 0
@@ -1503,30 +1518,29 @@ static int ec_clear_on_resume(const struct dmi_system_id *id)
        return 0;
 }
 
+static int ec_correct_ecdt(const struct dmi_system_id *id)
+{
+       pr_debug("Detected system needing ECDT address correction.\n");
+       EC_FLAGS_CORRECT_ECDT = 1;
+       return 0;
+}
+
 static struct dmi_system_id ec_dmi_table[] __initdata = {
        {
-       ec_skip_dsdt_scan, "Compal JFL92", {
-       DMI_MATCH(DMI_BIOS_VENDOR, "COMPAL"),
-       DMI_MATCH(DMI_BOARD_NAME, "JFL92") }, NULL},
+       ec_correct_ecdt, "Asus L4R", {
+       DMI_MATCH(DMI_BIOS_VERSION, "1008.006"),
+       DMI_MATCH(DMI_PRODUCT_NAME, "L4R"),
+       DMI_MATCH(DMI_BOARD_NAME, "L4R") }, NULL},
+       {
+       ec_correct_ecdt, "Asus M6R", {
+       DMI_MATCH(DMI_BIOS_VERSION, "0207"),
+       DMI_MATCH(DMI_PRODUCT_NAME, "M6R"),
+       DMI_MATCH(DMI_BOARD_NAME, "M6R") }, NULL},
        {
-       ec_validate_ecdt, "MSI MS-171F", {
+       ec_correct_ecdt, "MSI MS-171F", {
        DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star"),
        DMI_MATCH(DMI_PRODUCT_NAME, "MS-171F"),}, NULL},
        {
-       ec_validate_ecdt, "ASUS hardware", {
-       DMI_MATCH(DMI_BIOS_VENDOR, "ASUS") }, NULL},
-       {
-       ec_validate_ecdt, "ASUS hardware", {
-       DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer Inc.") }, NULL},
-       {
-       ec_skip_dsdt_scan, "HP Folio 13", {
-       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
-       DMI_MATCH(DMI_PRODUCT_NAME, "HP Folio 13"),}, NULL},
-       {
-       ec_validate_ecdt, "ASUS hardware", {
-       DMI_MATCH(DMI_SYS_VENDOR, "ASUSTek Computer Inc."),
-       DMI_MATCH(DMI_PRODUCT_NAME, "L4R"),}, NULL},
-       {
        ec_clear_on_resume, "Samsung hardware", {
        DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD.")}, NULL},
        {},
@@ -1534,8 +1548,8 @@ static struct dmi_system_id ec_dmi_table[] __initdata = {
 
 int __init acpi_ec_ecdt_probe(void)
 {
+       int ret = 0;
        acpi_status status;
-       struct acpi_ec *saved_ec = NULL;
        struct acpi_table_ecdt *ecdt_ptr;
 
        boot_ec = make_acpi_ec();
@@ -1547,67 +1561,45 @@ int __init acpi_ec_ecdt_probe(void)
        dmi_check_system(ec_dmi_table);
        status = acpi_get_table(ACPI_SIG_ECDT, 1,
                                (struct acpi_table_header **)&ecdt_ptr);
-       if (ACPI_SUCCESS(status)) {
-               pr_info("EC description table is found, configuring boot EC\n");
-               boot_ec->command_addr = ecdt_ptr->control.address;
-               boot_ec->data_addr = ecdt_ptr->data.address;
-               boot_ec->gpe = ecdt_ptr->gpe;
-               boot_ec->handle = ACPI_ROOT_OBJECT;
-               acpi_get_handle(ACPI_ROOT_OBJECT, ecdt_ptr->id,
-                               &boot_ec->handle);
-               /* Don't trust ECDT, which comes from ASUSTek */
-               if (!EC_FLAGS_VALIDATE_ECDT)
-                       goto install;
-               saved_ec = kmemdup(boot_ec, sizeof(struct acpi_ec), GFP_KERNEL);
-               if (!saved_ec)
-                       return -ENOMEM;
-       /* fall through */
+       if (ACPI_FAILURE(status)) {
+               ret = -ENODEV;
+               goto error;
        }
 
-       if (EC_FLAGS_SKIP_DSDT_SCAN) {
-               kfree(saved_ec);
-               return -ENODEV;
+       if (!ecdt_ptr->control.address || !ecdt_ptr->data.address) {
+               /*
+                * Asus X50GL:
+                * https://bugzilla.kernel.org/show_bug.cgi?id=11880
+                */
+               ret = -ENODEV;
+               goto error;
        }
 
-       /* This workaround is needed only on some broken machines,
-        * which require early EC, but fail to provide ECDT */
-       pr_debug("Look up EC in DSDT\n");
-       status = acpi_get_devices(ec_device_ids[0].id, ec_parse_device,
-                                       boot_ec, NULL);
-       /* Check that acpi_get_devices actually find something */
-       if (ACPI_FAILURE(status) || !boot_ec->handle)
-               goto error;
-       if (saved_ec) {
-               /* try to find good ECDT from ASUSTek */
-               if (saved_ec->command_addr != boot_ec->command_addr ||
-                   saved_ec->data_addr != boot_ec->data_addr ||
-                   saved_ec->gpe != boot_ec->gpe ||
-                   saved_ec->handle != boot_ec->handle)
-                       pr_info("ASUSTek keeps feeding us with broken "
-                       "ECDT tables, which are very hard to workaround. "
-                       "Trying to use DSDT EC info instead. Please send "
-                       "output of acpidump to linux-acpi@vger.kernel.org\n");
-               kfree(saved_ec);
-               saved_ec = NULL;
+       pr_info("EC description table is found, configuring boot EC\n");
+       if (EC_FLAGS_CORRECT_ECDT) {
+               /*
+                * Asus L4R, Asus M6R
+                * https://bugzilla.kernel.org/show_bug.cgi?id=9399
+                * MSI MS-171F
+                * https://bugzilla.kernel.org/show_bug.cgi?id=12461
+                */
+               boot_ec->command_addr = ecdt_ptr->data.address;
+               boot_ec->data_addr = ecdt_ptr->control.address;
        } else {
-               /* We really need to limit this workaround, the only ASUS,
-               * which needs it, has fake EC._INI method, so use it as flag.
-               * Keep boot_ec struct as it will be needed soon.
-               */
-               if (!dmi_name_in_vendors("ASUS") ||
-                   !acpi_has_method(boot_ec->handle, "_INI"))
-                       return -ENODEV;
+               boot_ec->command_addr = ecdt_ptr->control.address;
+               boot_ec->data_addr = ecdt_ptr->data.address;
        }
-install:
-       if (!ec_install_handlers(boot_ec)) {
+       boot_ec->gpe = ecdt_ptr->gpe;
+       boot_ec->handle = ACPI_ROOT_OBJECT;
+       ret = ec_install_handlers(boot_ec);
+       if (!ret)
                first_ec = boot_ec;
-               return 0;
-       }
 error:
-       kfree(boot_ec);
-       kfree(saved_ec);
-       boot_ec = NULL;
-       return -ENODEV;
+       if (ret) {
+               kfree(boot_ec);
+               boot_ec = NULL;
+       }
+       return ret;
 }
 
 static int param_set_event_clearing(const char *val, struct kernel_param *kp)