From 3db80c230da15ceb1a526438b458058abcd53800 Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Sun, 7 Feb 2016 10:00:31 -0500 Subject: [PATCH 01/14] ACPI: implement Generic Event Device Generic Event Device described in ACPI 6.1 allows platforms to handle platform interrupts in ACPI ASL statements. It borrows constructs like _EVT from GPIO events. All interrupts are listed in _CRS and the handler is written in _EVT method. Here is an example. Device (GED0) { Name (_HID, "ACPI0013") Name (_UID, 0) Name(_CRS, ResourceTemplate () { Interrupt(ResourceConsumer, Edge, ActiveHigh, Shared, , , ) {123} }) Method (_EVT, 1) { if (Lequal(123, Arg0)) { } } } Wake capability has not been implemented yet. Signed-off-by: Sinan Kaya Signed-off-by: Rafael J. Wysocki --- drivers/acpi/Makefile | 1 + drivers/acpi/evged.c | 156 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 drivers/acpi/evged.c diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index edeb2d1d99be..5a65f85cf7a7 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -47,6 +47,7 @@ acpi-$(CONFIG_ARM_AMBA) += acpi_amba.o acpi-y += int340x_thermal.o acpi-y += power.o acpi-y += event.o +acpi-$(CONFIG_ACPI_REDUCED_HARDWARE_ONLY) += evged.o acpi-y += sysfs.o acpi-y += property.o acpi-$(CONFIG_X86) += acpi_cmos_rtc.o diff --git a/drivers/acpi/evged.c b/drivers/acpi/evged.c new file mode 100644 index 000000000000..9c0a868d7b57 --- /dev/null +++ b/drivers/acpi/evged.c @@ -0,0 +1,156 @@ +/* + * Generic Event Device for ACPI. + * + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Generic Event Device allows platforms to handle interrupts in ACPI + * ASL statements. It follows very similar to _EVT method approach + * from GPIO events. All interrupts are listed in _CRS and the handler + * is written in _EVT method. Here is an example. + * + * Device (GED0) + * { + * + * Name (_HID, "ACPI0013") + * Name (_UID, 0) + * Method (_CRS, 0x0, Serialized) + * { + * Name (RBUF, ResourceTemplate () + * { + * Interrupt(ResourceConsumer, Edge, ActiveHigh, Shared, , , ) + * {123} + * } + * }) + * + * Method (_EVT, 1) { + * if (Lequal(123, Arg0)) + * { + * } + * } + * } + * + */ + +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "acpi-ged" + +struct acpi_ged_event { + struct list_head node; + struct device *dev; + unsigned int gsi; + unsigned int irq; + acpi_handle handle; +}; + +static irqreturn_t acpi_ged_irq_handler(int irq, void *data) +{ + struct acpi_ged_event *event = data; + acpi_status acpi_ret; + + acpi_ret = acpi_execute_simple_method(event->handle, NULL, event->gsi); + if (ACPI_FAILURE(acpi_ret)) + dev_err_once(event->dev, "IRQ method execution failed\n"); + + return IRQ_HANDLED; +} + +static acpi_status acpi_ged_request_interrupt(struct acpi_resource *ares, + void *context) +{ + struct acpi_ged_event *event; + unsigned int irq; + unsigned int gsi; + unsigned int irqflags = IRQF_ONESHOT; + struct device *dev = context; + acpi_handle handle = ACPI_HANDLE(dev); + acpi_handle evt_handle; + struct resource r; + struct acpi_resource_irq *p = &ares->data.irq; + struct acpi_resource_extended_irq *pext = &ares->data.extended_irq; + + if (ares->type == ACPI_RESOURCE_TYPE_END_TAG) + return AE_OK; + + if (!acpi_dev_resource_interrupt(ares, 0, &r)) { + dev_err(dev, "unable to parse IRQ resource\n"); + return AE_ERROR; + } + if (ares->type == ACPI_RESOURCE_TYPE_IRQ) + gsi = p->interrupts[0]; + else + gsi = pext->interrupts[0]; + + irq = r.start; + + if (ACPI_FAILURE(acpi_get_handle(handle, "_EVT", &evt_handle))) { + dev_err(dev, "cannot locate _EVT method\n"); + return AE_ERROR; + } + + dev_info(dev, "GED listening GSI %u @ IRQ %u\n", gsi, irq); + + event = devm_kzalloc(dev, sizeof(*event), GFP_KERNEL); + if (!event) + return AE_ERROR; + + event->gsi = gsi; + event->dev = dev; + event->irq = irq; + event->handle = evt_handle; + + if (r.flags & IORESOURCE_IRQ_SHAREABLE) + irqflags |= IRQF_SHARED; + + if (devm_request_threaded_irq(dev, irq, NULL, acpi_ged_irq_handler, + irqflags, "ACPI:Ged", event)) { + dev_err(dev, "failed to setup event handler for irq %u\n", irq); + return AE_ERROR; + } + + return AE_OK; +} + +static int ged_probe(struct platform_device *pdev) +{ + acpi_status acpi_ret; + + acpi_ret = acpi_walk_resources(ACPI_HANDLE(&pdev->dev), "_CRS", + acpi_ged_request_interrupt, &pdev->dev); + if (ACPI_FAILURE(acpi_ret)) { + dev_err(&pdev->dev, "unable to parse the _CRS record\n"); + return -EINVAL; + } + + return 0; +} + +static const struct acpi_device_id ged_acpi_ids[] = { + {"ACPI0013"}, + {}, +}; + +static struct platform_driver ged_driver = { + .probe = ged_probe, + .driver = { + .name = MODULE_NAME, + .acpi_match_table = ACPI_PTR(ged_acpi_ids), + }, +}; + +module_platform_driver(ged_driver); +MODULE_LICENSE("GPL v2"); From 1373718194ebebc43c00d8f117e03885749495b0 Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Tue, 22 Mar 2016 08:51:10 +0800 Subject: [PATCH 02/14] ACPI / PM: Introduce efi poweroff for HW-full platforms without _S5 The problem is Linux registers pm_power_off = efi_power_off only if we are in hardware reduced mode. Actually, what we also want is to do this when ACPI S5 is simply not supported on non-legacy platforms. Since some future Intel platforms are HW-full mode where the DSDT fails to supply an _S5 object(without SLP_TYP), we should let such kind of platform to leverage efi runtime service to poweroff. This patch uses efi power off as first choice when S5 is unavailable, even if there is a customized poweroff(driver provided, eg). Meanwhile, the legacy platforms will not be affected because there is no path for them to overwrite the pm_power_off to efi power off. Suggested-by: Len Brown Reviewed-by: Matt Fleming Signed-off-by: Chen Yu Signed-off-by: Rafael J. Wysocki --- arch/x86/platform/efi/quirks.c | 2 +- drivers/acpi/sleep.c | 7 +++++++ include/linux/acpi.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c index ab50ada1d56e..818d12ad7761 100644 --- a/arch/x86/platform/efi/quirks.c +++ b/arch/x86/platform/efi/quirks.c @@ -373,5 +373,5 @@ bool efi_reboot_required(void) bool efi_poweroff_required(void) { - return !!acpi_gbl_reduced_hardware; + return acpi_gbl_reduced_hardware || acpi_no_s5; } diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 2a8b59644297..7a2e4d45b266 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -26,6 +26,11 @@ #include "internal.h" #include "sleep.h" +/* + * Some HW-full platforms do not have _S5, so they may need + * to leverage efi power off for a shutdown. + */ +bool acpi_no_s5; static u8 sleep_states[ACPI_S_STATE_COUNT]; static void acpi_sleep_tts_switch(u32 acpi_state) @@ -882,6 +887,8 @@ int __init acpi_sleep_init(void) sleep_states[ACPI_STATE_S5] = 1; pm_power_off_prepare = acpi_power_off_prepare; pm_power_off = acpi_power_off; + } else { + acpi_no_s5 = true; } supported[0] = 0; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 06ed7e54033e..4d2e67f633b6 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -278,6 +278,7 @@ void acpi_irq_stats_init(void); extern u32 acpi_irq_handled; extern u32 acpi_irq_not_handled; extern unsigned int acpi_sci_irq; +extern bool acpi_no_s5; #define INVALID_ACPI_IRQ ((unsigned)-1) static inline bool acpi_sci_irq_valid(void) { From 0e1affe41bdd7b1bef64c007d260e142bcaef220 Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Thu, 24 Mar 2016 10:42:47 +0800 Subject: [PATCH 03/14] ACPI 2.0 / ECDT: Split EC_FLAGS_HANDLERS_INSTALLED This patch splits EC_FLAGS_HANDLERS_INSTALLED so that address space handler can be installed when it is not possible to install GPE handler during early stage. This patch also tunes address space handler installation, making it happening earlier than GPE handler installation for the same purpose. Since acpi_ec_start()/acpi_ec_stop() will be entered multiple times after applying this change, it is also required to protect acpi_enable_gpe()/ acpi_disable_gpe() invocations. Link: https://bugzilla.kernel.org/show_bug.cgi?id=112911 Signed-off-by: Lv Zheng Tested-by: Chris Bainbridge Signed-off-by: Rafael J. Wysocki --- drivers/acpi/ec.c | 96 +++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index b420fb46669d..b8f474b78bc7 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -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 @@ -367,7 +367,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 +377,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 +1289,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) @@ -1434,7 +1448,7 @@ ec_parse_io_ports(struct acpi_resource *resource, void *context) int __init acpi_boot_ec_enable(void) { - if (!boot_ec || test_bit(EC_FLAGS_HANDLERS_INSTALLED, &boot_ec->flags)) + if (!boot_ec) return 0; if (!ec_install_handlers(boot_ec)) { first_ec = boot_ec; From 59f0aa9480cfef9173a648cec4537addc5f3ad94 Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Thu, 24 Mar 2016 10:42:53 +0800 Subject: [PATCH 04/14] ACPI 2.0 / ECDT: Remove early namespace reference from EC All operation region accesses are allowed by AML interpreter when AML is executed, so actually BIOSen are responsible to avoid the operation region accesses in AML before OSPM has prepared an operation region driver. This is done via _REG control method. So AML code normally sets a global named object REGC to 1 when _REG(3, 1) is evaluated. Then what is ECDT? Quoting from ACPI spec 6.0, 5.2.15 Embedded Controller Boot Resources Table (ECDT): "The presence of this table allows OSPM to provide Embedded Controller operation region space access before the namespace has been evaluated." Spec also suggests a compatible mean to indicate the early EC access availability: Device (EC) { Name (REGC, Ones) Method (_REG, 2) { If (LEqual (Arg0, 3)) { Store (Arg1, REGC) } } Method (ECAV) { If (LEqual (REGC, Ones)) { If (LGreaterEqual (_REV, 2)) { Return (One) } Else { Return (Zero) } } Else { Return (REGC) } } } In this way, it allows EC accesses to happen before EC._REG(3, 1) is invoked. But ECAV is not the only way practical BIOSen using to indicate the early EC access availibility, the known variations include: 1. Setting REGC to One in \_SB._INI when _REV >= 2. Since \_SB._INI is the first control method evaluated by OSPM during the enumeration, this allows EC accesses to happen for the entire enumeration process before the namespace EC is enumerated. 2. Initialize REGC to One by default, this even allows EC accesses to happen during the table loading. Linux is now broken around ECDT support during the long term bug fixing work because it has merged many wrong ECDT bug fixes (see details below). Linux currently uses namespace EC's settings instead of ECDT settings when ECDT is detected. This apparently will result in namespace walk and _CRS/_GPE/_REG evaluations. Such stuffs could only happen after namespace is ready, while ECDT is purposely to be used before namespace is ready. The wrong bug fixing story is: 1. Link 1: At Linux ACPI early stages, "no _Lxx/_Exx/_Qxx evaluation can happen before the namespace is ready" are not ensured by ACPICA core and Linux. This is currently ensured by deferred enabling of GPE and defered registering of EC query methods (acpi_ec_register_query_methods). 2. Link 2: Reporters reported buggy ECDTs, expecting quirks for the platform. Originally, the quirk is simple, only doing things with ECDT. Bug 9399 and 12461 are platforms (Asus L4R, Asus M6R, MSI MS-171F) reported to have wrong ECDT IO port addresses, the port addresses are reversed. Bug 11880 is a platform (Asus X50GL) reported to have 0 valued port addresses, we can see that all EC accesses are protected by ECAV on this platform, so actually no early EC accesses is required by this platform. 3. Link 3: But when the bug fixing developer was requested to provide a handy and non-quirk bug fix, he tried to use correct EC settings from namespace and broke the spec purpose. We can even see that the developer was suffered from many regrssions. One interesting one is 14086, where the actual root cause obviously should be: _REG is evaluated too early. But unfortunately, the bug is fixed in a totally wrong way. So everything goes wrong from these commits: Commit: c6cb0e878446c79f42e7833d7bb69ed6bfbb381f Subject: ACPI: EC: Don't trust ECDT tables from ASUS Commit: a5032bfdd9c80e0231a6324661e123818eb46ecd Subject: ACPI: EC: Always parse EC device This patch reverts Linux behavior to simple ECDT quirk support in order to stop early _CRS/_GPE/_REG evaluations. For Bug 9399, 12461, since it is reported that the platforms require early EC accesses, this patch restores the simple ECDT quirks for them. For Bug 11880, since it is not reported that the platform requires early EC accesses and its ACPI tables contain correct ECAV, we choose an ECDT enumeration failure for this platform. Link 1: https://bugzilla.kernel.org/show_bug.cgi?id=9916 http://bugzilla.kernel.org/show_bug.cgi?id=10100 https://lkml.org/lkml/2008/2/25/282 Link 2: https://bugzilla.kernel.org/show_bug.cgi?id=9399 https://bugzilla.kernel.org/show_bug.cgi?id=12461 https://bugzilla.kernel.org/show_bug.cgi?id=11880 Link 3: https://bugzilla.kernel.org/show_bug.cgi?id=11884 https://bugzilla.kernel.org/show_bug.cgi?id=14081 https://bugzilla.kernel.org/show_bug.cgi?id=14086 https://bugzilla.kernel.org/show_bug.cgi?id=14446 Link 4: https://bugzilla.kernel.org/show_bug.cgi?id=112911 Signed-off-by: Lv Zheng Tested-by: Chris Bainbridge Signed-off-by: Rafael J. Wysocki --- drivers/acpi/ec.c | 151 +++++++++++++++++----------------------------- 1 file changed, 57 insertions(+), 94 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index b8f474b78bc7..0e70181f150c 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -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 @@ -1358,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) @@ -1462,20 +1462,6 @@ static const struct acpi_device_id ec_device_ids[] = { {"", 0}, }; -/* Some BIOS do not survive early DSDT scan, skip it */ -static int ec_skip_dsdt_scan(const struct dmi_system_id *id) -{ - EC_FLAGS_SKIP_DSDT_SCAN = 1; - return 0; -} - -/* 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 0 /* * Some EC firmware variations refuses to respond QR_EC when SCI_EVT is not @@ -1517,30 +1503,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_validate_ecdt, "MSI MS-171F", { + 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_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}, {}, @@ -1548,8 +1533,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(); @@ -1561,67 +1546,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"); + if (ACPI_FAILURE(status)) { + ret = -ENODEV; + goto error; + } + + if (!ecdt_ptr->control.address || !ecdt_ptr->data.address) { + /* + * Asus X50GL: + * https://bugzilla.kernel.org/show_bug.cgi?id=11880 + */ + ret = -ENODEV; + goto error; + } + + 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 { 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 (EC_FLAGS_SKIP_DSDT_SCAN) { - kfree(saved_ec); - return -ENODEV; - } - - /* 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; - } 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; - } -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) From fe6cbea0f096bfdb7eafdc7b937570cea8fca00e Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Thu, 24 Mar 2016 10:43:00 +0800 Subject: [PATCH 05/14] ACPI 2.0 / ECDT: Enable correct ECDT initialization order With wrong ECDT fixes reverted, it is possible to put ECDT probing before acpi_enable_subsystem(). But the ultimate purpose of ECDT re-enabling is to put the ECDT probing before the namespace initialization (acpi_load_tables()). This patch achieves this with protections so that we can enable it later when all necessary corrections are upstreamed. Link 4: https://bugzilla.kernel.org/show_bug.cgi?id=112911 Signed-off-by: Lv Zheng Tested-by: Chris Bainbridge Signed-off-by: Rafael J. Wysocki --- drivers/acpi/bus.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index c068c829b453..31e8da648fff 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -925,11 +925,13 @@ void __init acpi_early_init(void) goto error0; } - status = acpi_load_tables(); - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX - "Unable to load the System Description Tables\n"); - goto error0; + if (acpi_gbl_group_module_level_code) { + status = acpi_load_tables(); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX + "Unable to load the System Description Tables\n"); + goto error0; + } } #ifdef CONFIG_X86 @@ -995,17 +997,10 @@ static int __init acpi_bus_init(void) acpi_os_initialize1(); - status = acpi_enable_subsystem(ACPI_NO_ACPI_ENABLE); - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX - "Unable to start the ACPI Interpreter\n"); - goto error1; - } - /* * ACPI 2.0 requires the EC driver to be loaded and work before - * the EC device is found in the namespace (i.e. before acpi_initialize_objects() - * is called). + * the EC device is found in the namespace (i.e. before + * acpi_load_tables() is called). * * This is accomplished by looking for the ECDT table, and getting * the EC parameters out of that. @@ -1013,6 +1008,22 @@ static int __init acpi_bus_init(void) status = acpi_ec_ecdt_probe(); /* Ignore result. Not having an ECDT is not fatal. */ + if (!acpi_gbl_group_module_level_code) { + status = acpi_load_tables(); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX + "Unable to load the System Description Tables\n"); + goto error1; + } + } + + status = acpi_enable_subsystem(ACPI_NO_ACPI_ENABLE); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX + "Unable to start the ACPI Interpreter\n"); + goto error1; + } + status = acpi_initialize_objects(ACPI_FULL_INITIALIZATION); if (ACPI_FAILURE(status)) { printk(KERN_ERR PREFIX "Unable to initialize ACPI objects\n"); From 3d4b7ae96d81dc8ed4ecd556118b632c2707ff08 Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Thu, 24 Mar 2016 10:43:08 +0800 Subject: [PATCH 06/14] ACPI 2.0 / AML: Improve module level execution by moving the If/Else/While execution to per-table basis This experiment moves module level If/Else/While executions to per-table basis. If regressions are found against the enabling of this experimental improvement, this patch is the only one that should get bisected out. Please report the regressions to the kernel bugzilla for further root causing. Link: https://bugzilla.kernel.org/show_bug.cgi?id=112911 Signed-off-by: Lv Zheng Tested-by: Chris Bainbridge Signed-off-by: Rafael J. Wysocki --- include/acpi/acpixf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/acpixf.h b/include/acpi/acpixf.h index 17556979dc79..3d910e5e05f2 100644 --- a/include/acpi/acpixf.h +++ b/include/acpi/acpixf.h @@ -192,7 +192,7 @@ ACPI_INIT_GLOBAL(u8, acpi_gbl_do_not_use_xsdt, FALSE); /* * Optionally support group module level code. */ -ACPI_INIT_GLOBAL(u8, acpi_gbl_group_module_level_code, TRUE); +ACPI_INIT_GLOBAL(u8, acpi_gbl_group_module_level_code, FALSE); /* * Optionally use 32-bit FADT addresses if and when there is a conflict From cdd9a6b2a7461021a5ee24bc130592c1bdc54cd8 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 19 Apr 2016 18:27:46 -0700 Subject: [PATCH 07/14] ACPI / APD: Remove CLK_IS_ROOT This flag is a no-op now (see commit 47b0eeb3dc8a "clk: Deprecate CLK_IS_ROOT", 2016-02-02) so remove it. Signed-off-by: Stephen Boyd Acked-by: Mika Westerberg Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_apd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index f245bf35bedb..1daf9c46df8e 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -62,8 +62,7 @@ static int acpi_apd_setup(struct apd_private_data *pdata) if (dev_desc->fixed_clk_rate) { clk = clk_register_fixed_rate(&pdata->adev->dev, dev_name(&pdata->adev->dev), - NULL, CLK_IS_ROOT, - dev_desc->fixed_clk_rate); + NULL, 0, dev_desc->fixed_clk_rate); clk_register_clkdev(clk, NULL, dev_name(&pdata->adev->dev)); pdata->clk = clk; } From 628e35baa8e1e19217eb11b6ccfa96c273757d18 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 19 Apr 2016 18:28:38 -0700 Subject: [PATCH 08/14] ACPI / amba: Remove CLK_IS_ROOT This flag is a no-op now (see commit 47b0eeb3dc8a "clk: Deprecate CLK_IS_ROOT", 2016-02-02) so remove it. Signed-off-by: Stephen Boyd Acked-by: Graeme Gregory Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_amba.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_amba.c b/drivers/acpi/acpi_amba.c index 2a61b54ab968..7f77c071709a 100644 --- a/drivers/acpi/acpi_amba.c +++ b/drivers/acpi/acpi_amba.c @@ -35,8 +35,7 @@ static void amba_register_dummy_clk(void) if (amba_dummy_clk) return; - amba_dummy_clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, - CLK_IS_ROOT, 0); + amba_dummy_clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, 0, 0); clk_register_clkdev(amba_dummy_clk, "apb_pclk", NULL); } From f6a4790a5471d7cba406d87f6b41323f40bb93d2 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 27 Apr 2016 20:45:02 +0800 Subject: [PATCH 09/14] video / backlight: add two APIs for drivers to use It is useful to get the backlight device's pointer and use it to set backlight in some cases(the following patch will make use of it) so add the two APIs and export them. Signed-off-by: Aaron Lu Acked-by: Jingoo Han Signed-off-by: Rafael J. Wysocki --- drivers/video/backlight/backlight.c | 53 ++++++++++++++++++----------- include/linux/backlight.h | 2 ++ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index bddc8b17a4d8..18901b9b1eb4 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -164,6 +164,30 @@ static ssize_t brightness_show(struct device *dev, return sprintf(buf, "%d\n", bd->props.brightness); } +int backlight_device_set_brightness(struct backlight_device *bd, + unsigned long brightness) +{ + int rc = -ENXIO; + + mutex_lock(&bd->ops_lock); + if (bd->ops) { + if (brightness > bd->props.max_brightness) + rc = -EINVAL; + else { + pr_debug("set brightness to %lu\n", brightness); + bd->props.brightness = brightness; + backlight_update_status(bd); + rc = 0; + } + } + mutex_unlock(&bd->ops_lock); + + backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS); + + return rc; +} +EXPORT_SYMBOL(backlight_device_set_brightness); + static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -175,24 +199,9 @@ static ssize_t brightness_store(struct device *dev, if (rc) return rc; - rc = -ENXIO; + rc = backlight_device_set_brightness(bd, brightness); - mutex_lock(&bd->ops_lock); - if (bd->ops) { - if (brightness > bd->props.max_brightness) - rc = -EINVAL; - else { - pr_debug("set brightness to %lu\n", brightness); - bd->props.brightness = brightness; - backlight_update_status(bd); - rc = count; - } - } - mutex_unlock(&bd->ops_lock); - - backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS); - - return rc; + return rc ? rc : count; } static DEVICE_ATTR_RW(brightness); @@ -380,7 +389,7 @@ struct backlight_device *backlight_device_register(const char *name, } EXPORT_SYMBOL(backlight_device_register); -bool backlight_device_registered(enum backlight_type type) +struct backlight_device *backlight_device_get_by_type(enum backlight_type type) { bool found = false; struct backlight_device *bd; @@ -394,7 +403,13 @@ bool backlight_device_registered(enum backlight_type type) } mutex_unlock(&backlight_dev_list_mutex); - return found; + return found ? bd : NULL; +} +EXPORT_SYMBOL(backlight_device_get_by_type); + +bool backlight_device_registered(enum backlight_type type) +{ + return backlight_device_get_by_type(type) ? true : false; } EXPORT_SYMBOL(backlight_device_registered); diff --git a/include/linux/backlight.h b/include/linux/backlight.h index 1e7a69adbe6f..f46b88fa4a09 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -144,6 +144,8 @@ extern void backlight_force_update(struct backlight_device *bd, extern bool backlight_device_registered(enum backlight_type type); extern int backlight_register_notifier(struct notifier_block *nb); extern int backlight_unregister_notifier(struct notifier_block *nb); +extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type); +extern int backlight_device_set_brightness(struct backlight_device *bd, unsigned long brightness); #define to_backlight_device(obj) container_of(obj, struct backlight_device, dev) From 01c3664de62f89f6777e59173ad8e20b5a4c267f Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 27 Apr 2016 20:45:03 +0800 Subject: [PATCH 10/14] video / backlight: remove the backlight_device_registered API Since we will need the backlight_device_get_by_type API, we can use it instead of the backlight_device_registered API whenever necessary so remove the backlight_device_registered API. Signed-off-by: Aaron Lu Acked-by: Jingoo Han Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 2 +- drivers/video/backlight/backlight.c | 6 ------ include/linux/backlight.h | 1 - 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 1316ddd92fac..3d1327615f72 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -358,7 +358,7 @@ enum acpi_backlight_type acpi_video_get_backlight_type(void) if (!(video_caps & ACPI_VIDEO_BACKLIGHT)) return acpi_backlight_vendor; - if (acpi_osi_is_win8() && backlight_device_registered(BACKLIGHT_RAW)) + if (acpi_osi_is_win8() && backlight_device_get_by_type(BACKLIGHT_RAW)) return acpi_backlight_native; return acpi_backlight_video; diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index 18901b9b1eb4..288318ad21dd 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -407,12 +407,6 @@ struct backlight_device *backlight_device_get_by_type(enum backlight_type type) } EXPORT_SYMBOL(backlight_device_get_by_type); -bool backlight_device_registered(enum backlight_type type) -{ - return backlight_device_get_by_type(type) ? true : false; -} -EXPORT_SYMBOL(backlight_device_registered); - /** * backlight_device_unregister - unregisters a backlight device object. * @bd: the backlight device object to be unregistered and freed. diff --git a/include/linux/backlight.h b/include/linux/backlight.h index f46b88fa4a09..5f2fd61ef4fb 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -141,7 +141,6 @@ extern void devm_backlight_device_unregister(struct device *dev, struct backlight_device *bd); extern void backlight_force_update(struct backlight_device *bd, enum backlight_update_reason reason); -extern bool backlight_device_registered(enum backlight_type type); extern int backlight_register_notifier(struct notifier_block *nb); extern int backlight_unregister_notifier(struct notifier_block *nb); extern struct backlight_device *backlight_device_get_by_type(enum backlight_type type); From 059500940defe285222d3b189b366dfe7f299cae Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 27 Apr 2016 20:45:04 +0800 Subject: [PATCH 11/14] ACPI/video: export acpi_video_get_levels The acpi_video_get_levels is useful for other drivers, i.e. the to-be-added int3406 thermal driver, so export it. Signed-off-by: Aaron Lu Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_video.c | 83 +++++++++++++++++++++------------------ include/acpi/video.h | 20 ++++++++++ 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 4361bc98ef4c..3d5b8a099351 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -191,19 +191,6 @@ struct acpi_video_device_cap { u8 _DDC:1; /* Return the EDID for this device */ }; -struct acpi_video_brightness_flags { - u8 _BCL_no_ac_battery_levels:1; /* no AC/Battery levels in _BCL */ - u8 _BCL_reversed:1; /* _BCL package is in a reversed order */ - u8 _BQC_use_index:1; /* _BQC returns an index value */ -}; - -struct acpi_video_device_brightness { - int curr; - int count; - int *levels; - struct acpi_video_brightness_flags flags; -}; - struct acpi_video_device { unsigned long device_id; struct acpi_video_device_flags flags; @@ -325,7 +312,7 @@ static const struct thermal_cooling_device_ops video_cooling_ops = { */ static int -acpi_video_device_lcd_query_levels(struct acpi_video_device *device, +acpi_video_device_lcd_query_levels(acpi_handle handle, union acpi_object **levels) { int status; @@ -335,7 +322,7 @@ acpi_video_device_lcd_query_levels(struct acpi_video_device *device, *levels = NULL; - status = acpi_evaluate_object(device->dev->handle, "_BCL", NULL, &buffer); + status = acpi_evaluate_object(handle, "_BCL", NULL, &buffer); if (!ACPI_SUCCESS(status)) return status; obj = (union acpi_object *)buffer.pointer; @@ -766,36 +753,28 @@ static int acpi_video_bqc_quirk(struct acpi_video_device *device, return 0; } - -/* - * Arg: - * device : video output device (LCD, CRT, ..) - * - * Return Value: - * Maximum brightness level - * - * Allocate and initialize device->brightness. - */ - -static int -acpi_video_init_brightness(struct acpi_video_device *device) +int acpi_video_get_levels(struct acpi_device *device, + struct acpi_video_device_brightness **dev_br) { union acpi_object *obj = NULL; int i, max_level = 0, count = 0, level_ac_battery = 0; - unsigned long long level, level_old; union acpi_object *o; struct acpi_video_device_brightness *br = NULL; - int result = -EINVAL; + int result = 0; u32 value; - if (!ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device, &obj))) { + if (!ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device->handle, + &obj))) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Could not query available " "LCD brightness level\n")); + result = -ENODEV; goto out; } - if (obj->package.count < 2) + if (obj->package.count < 2) { + result = -EINVAL; goto out; + } br = kzalloc(sizeof(*br), GFP_KERNEL); if (!br) { @@ -861,6 +840,38 @@ acpi_video_init_brightness(struct acpi_video_device *device) "Found unordered _BCL package")); br->count = count; + *dev_br = br; + +out: + kfree(obj); + return result; +out_free: + kfree(br); + goto out; +} +EXPORT_SYMBOL(acpi_video_get_levels); + +/* + * Arg: + * device : video output device (LCD, CRT, ..) + * + * Return Value: + * Maximum brightness level + * + * Allocate and initialize device->brightness. + */ + +static int +acpi_video_init_brightness(struct acpi_video_device *device) +{ + int i, max_level = 0; + unsigned long long level, level_old; + struct acpi_video_device_brightness *br = NULL; + int result = -EINVAL; + + result = acpi_video_get_levels(device->dev, &br); + if (result) + return result; device->brightness = br; /* _BQC uses INDEX while _BCL uses VALUE in some laptops */ @@ -903,17 +914,13 @@ acpi_video_init_brightness(struct acpi_video_device *device) goto out_free_levels; ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "found %d brightness levels\n", count - 2)); - kfree(obj); - return result; + "found %d brightness levels\n", br->count - 2)); + return 0; out_free_levels: kfree(br->levels); -out_free: kfree(br); -out: device->brightness = NULL; - kfree(obj); return result; } diff --git a/include/acpi/video.h b/include/acpi/video.h index 5ca2f2c16458..a4b96c971564 100644 --- a/include/acpi/video.h +++ b/include/acpi/video.h @@ -4,6 +4,19 @@ #include /* for ENODEV */ #include /* for bool */ +struct acpi_video_brightness_flags { + u8 _BCL_no_ac_battery_levels:1; /* no AC/Battery levels in _BCL */ + u8 _BCL_reversed:1; /* _BCL package is in a reversed order */ + u8 _BQC_use_index:1; /* _BQC returns an index value */ +}; + +struct acpi_video_device_brightness { + int curr; + int count; + int *levels; + struct acpi_video_brightness_flags flags; +}; + struct acpi_device; #define ACPI_VIDEO_CLASS "video" @@ -37,6 +50,8 @@ extern void acpi_video_set_dmi_backlight_type(enum acpi_backlight_type type); * may change over time and should not be cached. */ extern bool acpi_video_handles_brightness_key_presses(void); +extern int acpi_video_get_levels(struct acpi_device *device, + struct acpi_video_device_brightness **dev_br); #else static inline int acpi_video_register(void) { return 0; } static inline void acpi_video_unregister(void) { return; } @@ -56,6 +71,11 @@ static inline bool acpi_video_handles_brightness_key_presses(void) { return false; } +static int acpi_video_get_levels(struct acpi_device *device, + struct acpi_video_device_brightness **dev_br) +{ + return -ENODEV; +} #endif #endif From 437014bdac9680e3b32c006635ac270b808ce476 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 9 May 2016 14:40:27 -0400 Subject: [PATCH 12/14] ACPI / GED: make evged.c explicitly non-modular The Makefile/Kconfig currently controlling compilation of this code is: Makefile:acpi-$(CONFIG_ACPI_REDUCED_HARDWARE_ONLY) += evged.o drivers/acpi/Kconfig:config ACPI_REDUCED_HARDWARE_ONLY drivers/acpi/Kconfig: bool "Hardware-reduced ACPI support only" if EXPERT ...meaning that it currently is not being built as a module by anyone. Lets remove the couple traces of modularity so that when reading the code there is no doubt it is builtin-only. Since module_platform_driver() uses the same init level priority as builtin_platform_driver() the init ordering remains unchanged with this commit. The file wasn't explicitly including the module.h file but it did already have init.h so, unlike similar changes, this one has no header changes at all. We also delete the MODULE_LICENSE tag since all that information is already contained at the top of the file in the comments. Signed-off-by: Paul Gortmaker Signed-off-by: Rafael J. Wysocki --- drivers/acpi/evged.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/acpi/evged.c b/drivers/acpi/evged.c index 9c0a868d7b57..46f060356a22 100644 --- a/drivers/acpi/evged.c +++ b/drivers/acpi/evged.c @@ -151,6 +151,4 @@ static struct platform_driver ged_driver = { .acpi_match_table = ACPI_PTR(ged_acpi_ids), }, }; - -module_platform_driver(ged_driver); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver(ged_driver); From a3c89334f06bacf7e2f23203ad64cfd6e78dbbf4 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Mon, 9 May 2016 15:54:58 +0800 Subject: [PATCH 13/14] Thermal / ACPI / video: add INT3406 thermal driver INT3406 ACPI device object resembles an ACPI video output device, but its _BCM is said to be deprecated and should not be used. So we will make use of the raw interface to do the actual cooling. Signed-off-by: Aaron Lu Acked-by: Zhang Rui Signed-off-by: Rafael J. Wysocki --- drivers/thermal/Kconfig | 28 +-- drivers/thermal/int340x_thermal/Kconfig | 42 ++++ drivers/thermal/int340x_thermal/Makefile | 1 + .../thermal/int340x_thermal/int3406_thermal.c | 236 ++++++++++++++++++ 4 files changed, 282 insertions(+), 25 deletions(-) create mode 100644 drivers/thermal/int340x_thermal/Kconfig create mode 100644 drivers/thermal/int340x_thermal/int3406_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3c3dc4a3d52c..d89d60c8b6cf 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -338,31 +338,9 @@ config INTEL_QUARK_DTS_THERMAL hot & critical. The critical trip point default value is set by underlying BIOS/Firmware. -config INT340X_THERMAL - tristate "ACPI INT340X thermal drivers" - depends on X86 && ACPI - select THERMAL_GOV_USER_SPACE - select ACPI_THERMAL_REL - select ACPI_FAN - select INTEL_SOC_DTS_IOSF_CORE - select THERMAL_WRITABLE_TRIPS - help - Newer laptops and tablets that use ACPI may have thermal sensors and - other devices with thermal control capabilities outside the core - CPU/SOC, for thermal safety reasons. - They are exposed for the OS to use via the INT3400 ACPI device object - as the master, and INT3401~INT340B ACPI device objects as the slaves. - Enable this to expose the temperature information and cooling ability - from these objects to userspace via the normal thermal framework. - This means that a wide range of applications and GUI widgets can show - the information to the user or use this information for making - decisions. For example, the Intel Thermal Daemon can use this - information to allow the user to select his laptop to run without - turning on the fans. - -config ACPI_THERMAL_REL - tristate - depends on ACPI +menu "ACPI INT340X thermal drivers" +source drivers/thermal/int340x_thermal/Kconfig +endmenu config INTEL_PCH_THERMAL tristate "Intel PCH Thermal Reporting Driver" diff --git a/drivers/thermal/int340x_thermal/Kconfig b/drivers/thermal/int340x_thermal/Kconfig new file mode 100644 index 000000000000..0582bd12a239 --- /dev/null +++ b/drivers/thermal/int340x_thermal/Kconfig @@ -0,0 +1,42 @@ +# +# ACPI INT340x thermal drivers configuration +# + +config INT340X_THERMAL + tristate "ACPI INT340X thermal drivers" + depends on X86 && ACPI + select THERMAL_GOV_USER_SPACE + select ACPI_THERMAL_REL + select ACPI_FAN + select INTEL_SOC_DTS_IOSF_CORE + help + Newer laptops and tablets that use ACPI may have thermal sensors and + other devices with thermal control capabilities outside the core + CPU/SOC, for thermal safety reasons. + They are exposed for the OS to use via the INT3400 ACPI device object + as the master, and INT3401~INT340B ACPI device objects as the slaves. + Enable this to expose the temperature information and cooling ability + from these objects to userspace via the normal thermal framework. + This means that a wide range of applications and GUI widgets can show + the information to the user or use this information for making + decisions. For example, the Intel Thermal Daemon can use this + information to allow the user to select his laptop to run without + turning on the fans. + +config ACPI_THERMAL_REL + tristate + depends on ACPI + +if INT340X_THERMAL + +config INT3406_THERMAL + tristate "ACPI INT3406 display thermal driver" + depends on ACPI_VIDEO + help + The display thermal device represents the LED/LCD display panel + that may or may not include touch support. The main function of + the display thermal device is to allow control of the display + brightness in order to address a thermal condition or to reduce + power consumed by display device. + +endif diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile index ba77a34f659f..df0df055e7ff 100644 --- a/drivers/thermal/int340x_thermal/Makefile +++ b/drivers/thermal/int340x_thermal/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o +obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o diff --git a/drivers/thermal/int340x_thermal/int3406_thermal.c b/drivers/thermal/int340x_thermal/int3406_thermal.c new file mode 100644 index 000000000000..13d431cbd29e --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3406_thermal.c @@ -0,0 +1,236 @@ +/* + * INT3406 thermal driver for display participant device + * + * Copyright (C) 2016, Intel Corporation + * Authors: Aaron Lu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80 + +struct int3406_thermal_data { + int upper_limit; + int upper_limit_index; + int lower_limit; + int lower_limit_index; + acpi_handle handle; + struct acpi_video_device_brightness *br; + struct backlight_device *raw_bd; + struct thermal_cooling_device *cooling_dev; +}; + +static int int3406_thermal_to_raw(int level, struct int3406_thermal_data *d) +{ + int max_level = d->br->levels[d->br->count - 1]; + int raw_max = d->raw_bd->props.max_brightness; + + return level * raw_max / max_level; +} + +static int int3406_thermal_to_acpi(int level, struct int3406_thermal_data *d) +{ + int raw_max = d->raw_bd->props.max_brightness; + int max_level = d->br->levels[d->br->count - 1]; + + return level * max_level / raw_max; +} + +static int +int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev, + unsigned long *state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + int index = d->lower_limit_index ? d->lower_limit_index : 2; + + *state = d->br->count - 1 - index; + return 0; +} + +static int +int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev, + unsigned long state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + int level, raw_level; + + if (state > d->br->count - 3) + return -EINVAL; + + state = d->br->count - 1 - state; + level = d->br->levels[state]; + + if ((d->upper_limit && level > d->upper_limit) || + (d->lower_limit && level < d->lower_limit)) + return -EINVAL; + + raw_level = int3406_thermal_to_raw(level, d); + return backlight_device_set_brightness(d->raw_bd, raw_level); +} + +static int +int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev, + unsigned long *state) +{ + struct int3406_thermal_data *d = cooling_dev->devdata; + int raw_level, level, i; + int *levels = d->br->levels; + + raw_level = d->raw_bd->props.brightness; + level = int3406_thermal_to_acpi(raw_level, d); + + /* + * There is no 1:1 mapping between the firmware interface level with the + * raw interface level, we will have to find one that is close enough. + */ + for (i = 2; i < d->br->count; i++) { + if (level < levels[i]) { + if (i == 2) + break; + if ((level - levels[i - 1]) < (levels[i] - level)) + i--; + break; + } + } + + *state = d->br->count - 1 - i; + return 0; +} + +static const struct thermal_cooling_device_ops video_cooling_ops = { + .get_max_state = int3406_thermal_get_max_state, + .get_cur_state = int3406_thermal_get_cur_state, + .set_cur_state = int3406_thermal_set_cur_state, +}; + +static int int3406_thermal_get_index(int *array, int nr, int value) +{ + int i; + + for (i = 0; i < nr; i++) { + if (array[i] == value) + break; + } + return i == nr ? -ENOENT : i; +} + +static void int3406_thermal_get_limit(struct int3406_thermal_data *d) +{ + acpi_status status; + unsigned long long lower_limit, upper_limit; + int index; + + status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit); + if (ACPI_SUCCESS(status)) { + index = int3406_thermal_get_index(d->br->levels, d->br->count, + lower_limit); + if (index > 0) { + d->lower_limit = (int)lower_limit; + d->lower_limit_index = index; + } + } + + status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit); + if (ACPI_SUCCESS(status)) { + index = int3406_thermal_get_index(d->br->levels, d->br->count, + upper_limit); + if (index > 0) { + d->upper_limit = (int)upper_limit; + d->upper_limit_index = index; + } + } +} + +static void int3406_notify(acpi_handle handle, u32 event, void *data) +{ + if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED) + int3406_thermal_get_limit(data); +} + +static int int3406_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3406_thermal_data *d; + struct backlight_device *bd; + int ret; + + if (!ACPI_HANDLE(&pdev->dev)) + return -ENODEV; + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + d->handle = ACPI_HANDLE(&pdev->dev); + + bd = backlight_device_get_by_type(BACKLIGHT_RAW); + if (!bd) + return -ENODEV; + d->raw_bd = bd; + + ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br); + if (ret) + return ret; + + int3406_thermal_get_limit(d); + + d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev), + d, &video_cooling_ops); + if (IS_ERR(d->cooling_dev)) + goto err; + + ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + int3406_notify, d); + if (ret) + goto err_cdev; + + platform_set_drvdata(pdev, d); + + return 0; + +err_cdev: + thermal_cooling_device_unregister(d->cooling_dev); +err: + kfree(d->br); + return -ENODEV; +} + +static int int3406_thermal_remove(struct platform_device *pdev) +{ + struct int3406_thermal_data *d = platform_get_drvdata(pdev); + + thermal_cooling_device_unregister(d->cooling_dev); + kfree(d->br); + return 0; +} + +static const struct acpi_device_id int3406_thermal_match[] = { + {"INT3406", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3406_thermal_match); + +static struct platform_driver int3406_thermal_driver = { + .probe = int3406_thermal_probe, + .remove = int3406_thermal_remove, + .driver = { + .name = "int3406 thermal", + .owner = THIS_MODULE, + .acpi_match_table = int3406_thermal_match, + }, +}; + +module_platform_driver(int3406_thermal_driver); + +MODULE_DESCRIPTION("INT3406 Thermal driver"); +MODULE_LICENSE("GPL v2"); From e4f35c1339f0cfcf38d3f63dd6fea2b070399263 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 12 May 2016 23:00:12 +0200 Subject: [PATCH 14/14] ACPI / video: mark acpi_video_get_levels() inline A recent patch added a stub function for acpi_video_get_levels when CONFIG_ACPI_VIDEO is disabled. However, this is marked as 'static' and causes a warning about an unused function whereever the header gets included: In file included from ../drivers/gpu/drm/radeon/radeon_acpi.c:28:0: include/acpi/video.h:74:12: error: 'acpi_video_get_levels' defined but not used [-Werror=unused-function] This makes the declaration 'static inline', which gets rid of the warning. Signed-off-by: Arnd Bergmann Fixes: 059500940def (ACPI/video: export acpi_video_get_levels) Signed-off-by: Rafael J. Wysocki --- include/acpi/video.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/video.h b/include/acpi/video.h index a4b96c971564..70a41f742037 100644 --- a/include/acpi/video.h +++ b/include/acpi/video.h @@ -71,7 +71,7 @@ static inline bool acpi_video_handles_brightness_key_presses(void) { return false; } -static int acpi_video_get_levels(struct acpi_device *device, +static inline int acpi_video_get_levels(struct acpi_device *device, struct acpi_video_device_brightness **dev_br) { return -ENODEV;