diff options
| author | Miguel Ojeda <ojeda@kernel.org> | 2026-08-16 15:32:33 +0200 |
|---|---|---|
| committer | Miguel Ojeda <ojeda@kernel.org> | 2026-08-23 21:27:17 +0200 |
| commit | 5febf432df1cfa5b25d99b54c32103fafbdd0eb9 (patch) | |
| tree | 93a1ed1d337bc53adf1e572c2586d347703b286e /drivers/acpi/x86 | |
| download | linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.tar.gz linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.zip | |
kbuild: rust: keep Rust objects out of Clang LTO with inline helpersgrafted
Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit
`objtool` errors such as:
vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a:
can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd
The reason is that in such builds, the Clang invocation that compiles
the combined Rust plus helpers bitcode emits LLVM bitcode (again) --
the final code generation happens in the linker's LTO step, which the
`-mllvm` trap options passed to Clang do not reach.
This, in turn, means that unreachable traps are missing, and the
impossible paths do not merely fallthrough to the next symbol, but past
the end of their own section, since LTO builds place each function in
its own section.
Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always
emits machine code directly, with the traps in place.
Assisted-by: LLM
Cc: Gary Guo <gary@garyguo.net>
Cc: Boqun Feng <boqun@kernel.org>
Cc: Alice Ryhl <aliceryhl@google.com>
Cc: Matthew Maurer <mmaurer@google.com>
Cc: Josh Poimboeuf <jpoimboe@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: stable@vger.kernel.org
Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust")
Acked-by: Gary Guo <gary@garyguo.net>
Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Diffstat (limited to 'drivers/acpi/x86')
| -rw-r--r-- | drivers/acpi/x86/Makefile | 8 | ||||
| -rw-r--r-- | drivers/acpi/x86/apple.c | 148 | ||||
| -rw-r--r-- | drivers/acpi/x86/blacklist.c | 140 | ||||
| -rw-r--r-- | drivers/acpi/x86/cmos_rtc.c | 106 | ||||
| -rw-r--r-- | drivers/acpi/x86/lpss.c | 1352 | ||||
| -rw-r--r-- | drivers/acpi/x86/s2idle.c | 682 | ||||
| -rw-r--r-- | drivers/acpi/x86/utils.c | 667 |
7 files changed, 3103 insertions, 0 deletions
diff --git a/drivers/acpi/x86/Makefile b/drivers/acpi/x86/Makefile new file mode 100644 index 000000000..63c99509e --- /dev/null +++ b/drivers/acpi/x86/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_ACPI) += acpi-x86.o +acpi-x86-y += apple.o +acpi-x86-y += cmos_rtc.o +acpi-x86-$(CONFIG_PCI) += lpss.o +acpi-x86-y += s2idle.o +acpi-x86-y += utils.o + +obj-$(CONFIG_X86) += blacklist.o diff --git a/drivers/acpi/x86/apple.c b/drivers/acpi/x86/apple.c new file mode 100644 index 000000000..45d0f16f3 --- /dev/null +++ b/drivers/acpi/x86/apple.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * apple.c - Apple ACPI quirks + * Copyright (C) 2017 Lukas Wunner <lukas@wunner.de> + */ + +#include <linux/acpi.h> +#include <linux/bitmap.h> +#include <linux/platform_data/x86/apple.h> +#include <linux/uuid.h> +#include "../internal.h" + +/* Apple _DSM device properties GUID */ +static const guid_t apple_prp_guid = + GUID_INIT(0xa0b5b7c6, 0x1318, 0x441c, + 0xb0, 0xc9, 0xfe, 0x69, 0x5e, 0xaf, 0x94, 0x9b); + +/** + * acpi_extract_apple_properties - retrieve and convert Apple _DSM properties + * @adev: ACPI device for which to retrieve the properties + * + * Invoke Apple's custom _DSM once to check the protocol version and once more + * to retrieve the properties. They are marshalled up in a single package as + * alternating key/value elements, unlike _DSD which stores them as a package + * of 2-element packages. Convert to _DSD format and make them available under + * the primary fwnode. + */ +void acpi_extract_apple_properties(struct acpi_device *adev) +{ + unsigned int i, j = 0, newsize = 0, numprops, numvalid; + union acpi_object *props, *newprops; + unsigned long *valid = NULL; + void *free_space; + + if (!x86_apple_machine) + return; + + props = acpi_evaluate_dsm_typed(adev->handle, &apple_prp_guid, 1, 0, + NULL, ACPI_TYPE_BUFFER); + if (!props) + return; + + if (!props->buffer.length) + goto out_free; + + if (props->buffer.pointer[0] != 3) { + acpi_handle_info(adev->handle, FW_INFO + "unsupported properties version %*ph\n", + props->buffer.length, props->buffer.pointer); + goto out_free; + } + + ACPI_FREE(props); + props = acpi_evaluate_dsm_typed(adev->handle, &apple_prp_guid, 1, 1, + NULL, ACPI_TYPE_PACKAGE); + if (!props) + return; + + numprops = props->package.count / 2; + if (!numprops) + goto out_free; + + valid = bitmap_zalloc(numprops, GFP_KERNEL); + if (!valid) + goto out_free; + + /* newsize = key length + value length of each tuple */ + for (i = 0; i < numprops; i++) { + union acpi_object *key = &props->package.elements[i * 2]; + union acpi_object *val = &props->package.elements[i * 2 + 1]; + + if ( key->type != ACPI_TYPE_STRING || + (val->type != ACPI_TYPE_INTEGER && + val->type != ACPI_TYPE_BUFFER && + val->type != ACPI_TYPE_STRING)) + continue; /* skip invalid properties */ + + __set_bit(i, valid); + newsize += key->string.length + 1; + if ( val->type == ACPI_TYPE_BUFFER) + newsize += val->buffer.length; + else if (val->type == ACPI_TYPE_STRING) + newsize += val->string.length + 1; + } + + numvalid = bitmap_weight(valid, numprops); + if (numprops > numvalid) + acpi_handle_info(adev->handle, FW_INFO + "skipped %u properties: wrong type\n", + numprops - numvalid); + if (numvalid == 0) + goto out_free; + + /* newsize += top-level package + 3 objects for each key/value tuple */ + newsize += (1 + 3 * numvalid) * sizeof(union acpi_object); + newprops = ACPI_ALLOCATE_ZEROED(newsize); + if (!newprops) + goto out_free; + + /* layout: top-level package | packages | key/value tuples | strings */ + newprops->type = ACPI_TYPE_PACKAGE; + newprops->package.count = numvalid; + newprops->package.elements = &newprops[1]; + free_space = &newprops[1 + 3 * numvalid]; + + for_each_set_bit(i, valid, numprops) { + union acpi_object *key = &props->package.elements[i * 2]; + union acpi_object *val = &props->package.elements[i * 2 + 1]; + unsigned int k = 1 + numvalid + j * 2; /* index into newprops */ + unsigned int v = k + 1; + + newprops[1 + j].type = ACPI_TYPE_PACKAGE; + newprops[1 + j].package.count = 2; + newprops[1 + j].package.elements = &newprops[k]; + + newprops[k].type = ACPI_TYPE_STRING; + newprops[k].string.length = key->string.length; + newprops[k].string.pointer = free_space; + memcpy(free_space, key->string.pointer, key->string.length); + free_space += key->string.length + 1; + + newprops[v].type = val->type; + if (val->type == ACPI_TYPE_INTEGER) { + newprops[v].integer.value = val->integer.value; + } else if (val->type == ACPI_TYPE_STRING) { + newprops[v].string.length = val->string.length; + newprops[v].string.pointer = free_space; + memcpy(free_space, val->string.pointer, + val->string.length); + free_space += val->string.length + 1; + } else { + newprops[v].buffer.length = val->buffer.length; + newprops[v].buffer.pointer = free_space; + memcpy(free_space, val->buffer.pointer, + val->buffer.length); + free_space += val->buffer.length; + } + j++; /* count valid properties */ + } + WARN_ON(free_space != (void *)newprops + newsize); + + adev->data.pointer = newprops; + acpi_data_add_props(&adev->data, &apple_prp_guid, newprops); + +out_free: + ACPI_FREE(props); + bitmap_free(valid); +} diff --git a/drivers/acpi/x86/blacklist.c b/drivers/acpi/x86/blacklist.c new file mode 100644 index 000000000..55214d0a1 --- /dev/null +++ b/drivers/acpi/x86/blacklist.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * blacklist.c + * + * Check to see if the given machine has a known bad ACPI BIOS + * or if the BIOS is too old. + * Check given machine against acpi_rev_dmi_table[]. + * + * Copyright (C) 2004 Len Brown <len.brown@intel.com> + * Copyright (C) 2002 Andy Grover <andrew.grover@intel.com> + */ + +#define pr_fmt(fmt) "ACPI: " fmt + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/dmi.h> + +#include "../internal.h" + +#ifdef CONFIG_DMI +static const struct dmi_system_id acpi_rev_dmi_table[] __initconst; +#endif + +/* + * POLICY: If *anything* doesn't work, put it on the blacklist. + * If they are critical errors, mark it critical, and abort driver load. + */ +static struct acpi_platform_list acpi_blacklist[] __initdata = { + /* Compaq Presario 1700 */ + {"PTLTD ", " DSDT ", 0x06040000, ACPI_SIG_DSDT, less_than_or_equal, + "Multiple problems", 1}, + /* Sony FX120, FX140, FX150? */ + {"SONY ", "U0 ", 0x20010313, ACPI_SIG_DSDT, less_than_or_equal, + "ACPI driver problem", 1}, + /* Compaq Presario 800, Insyde BIOS */ + {"INT440", "SYSFexxx", 0x00001001, ACPI_SIG_DSDT, less_than_or_equal, + "Does not use _REG to protect EC OpRegions", 1}, + /* IBM 600E - _ADR should return 7, but it returns 1 */ + {"IBM ", "TP600E ", 0x00000105, ACPI_SIG_DSDT, less_than_or_equal, + "Incorrect _ADR", 1}, + + { } +}; + +int __init acpi_blacklisted(void) +{ + int i; + int blacklisted = 0; + + i = acpi_match_platform_list(acpi_blacklist); + if (i >= 0) { + pr_err("Vendor \"%6.6s\" System \"%8.8s\" Revision 0x%x has a known ACPI BIOS problem.\n", + acpi_blacklist[i].oem_id, + acpi_blacklist[i].oem_table_id, + acpi_blacklist[i].oem_revision); + + pr_err("Reason: %s. This is a %s error\n", + acpi_blacklist[i].reason, + (acpi_blacklist[i].data ? + "non-recoverable" : "recoverable")); + + blacklisted = acpi_blacklist[i].data; + } + + (void)early_acpi_osi_init(); +#ifdef CONFIG_DMI + dmi_check_system(acpi_rev_dmi_table); +#endif + + return blacklisted; +} +#ifdef CONFIG_DMI +#ifdef CONFIG_ACPI_REV_OVERRIDE_POSSIBLE +static int __init dmi_enable_rev_override(const struct dmi_system_id *d) +{ + pr_notice("DMI detected: %s (force ACPI _REV to 5)\n", d->ident); + acpi_rev_override_setup(NULL); + return 0; +} +#endif + +static const struct dmi_system_id acpi_rev_dmi_table[] __initconst = { +#ifdef CONFIG_ACPI_REV_OVERRIDE_POSSIBLE + /* + * DELL XPS 13 (2015) switches sound between HDA and I2S + * depending on the ACPI _REV callback. If userspace supports + * I2S sufficiently (or if you do not care about sound), you + * can safely disable this quirk. + */ + { + .callback = dmi_enable_rev_override, + .ident = "DELL XPS 13 (2015)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 13 9343"), + }, + }, + { + .callback = dmi_enable_rev_override, + .ident = "DELL Precision 5520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Precision 5520"), + }, + }, + { + .callback = dmi_enable_rev_override, + .ident = "DELL Precision 3520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Precision 3520"), + }, + }, + /* + * Resolves a quirk with the Dell Latitude 3350 that + * causes the ethernet adapter to not function. + */ + { + .callback = dmi_enable_rev_override, + .ident = "DELL Latitude 3350", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude 3350"), + }, + }, + { + .callback = dmi_enable_rev_override, + .ident = "DELL Inspiron 7537", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"), + }, + }, +#endif + {} +}; + +#endif /* CONFIG_DMI */ diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c new file mode 100644 index 000000000..ced334e19 --- /dev/null +++ b/drivers/acpi/x86/cmos_rtc.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ACPI support for CMOS RTC Address Space access + * + * Copyright (C) 2013, Intel Corporation + * Authors: Lan Tianyu <tianyu.lan@intel.com> + */ + +#define pr_fmt(fmt) "ACPI: " fmt + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mc146818rtc.h> + +#include "../internal.h" + +static const struct acpi_device_id acpi_cmos_rtc_ids[] = { + { "ACPI000E", 1 }, /* ACPI Time and Alarm Device (TAD) */ + ACPI_CMOS_RTC_IDS +}; + +bool cmos_rtc_platform_device_present; + +static acpi_status acpi_cmos_rtc_space_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value64, + void *handler_context, + void *region_context) +{ + unsigned int i, bytes = DIV_ROUND_UP(bits, 8); + u8 *value = (u8 *)value64; + + if (address > 0xff || !value64) + return AE_BAD_PARAMETER; + + guard(spinlock_irq)(&rtc_lock); + + if (function == ACPI_WRITE) { + for (i = 0; i < bytes; i++, address++, value++) + CMOS_WRITE(*value, address); + + return AE_OK; + } + + if (function == ACPI_READ) { + for (i = 0; i < bytes; i++, address++, value++) + *value = CMOS_READ(address); + + return AE_OK; + } + + return AE_BAD_PARAMETER; +} + +static int acpi_install_cmos_rtc_space_handler(acpi_handle handle) +{ + static bool cmos_rtc_space_handler_present __read_mostly; + acpi_status status; + + if (cmos_rtc_space_handler_present) + return 0; + + status = acpi_install_address_space_handler(handle, + ACPI_ADR_SPACE_CMOS, + acpi_cmos_rtc_space_handler, + NULL, NULL); + if (ACPI_FAILURE(status)) { + pr_err("Failed to install CMOS-RTC address space handler\n"); + return -ENODEV; + } + + cmos_rtc_space_handler_present = true; + + return 1; +} + +static int acpi_cmos_rtc_attach(struct acpi_device *adev, + const struct acpi_device_id *id) +{ + int ret; + + ret = acpi_install_cmos_rtc_space_handler(adev->handle); + if (ret < 0) + return ret; + + if (IS_ERR_OR_NULL(acpi_create_platform_device(adev, NULL))) { + pr_err("Failed to create a platform device for %s\n", (char *)id->id); + return 0; + } else if (!id->driver_data) { + cmos_rtc_platform_device_present = true; + } + return 1; +} + +static struct acpi_scan_handler cmos_rtc_handler = { + .ids = acpi_cmos_rtc_ids, + .attach = acpi_cmos_rtc_attach, +}; + +void __init acpi_cmos_rtc_init(void) +{ + acpi_scan_add_handler(&cmos_rtc_handler); +} diff --git a/drivers/acpi/x86/lpss.c b/drivers/acpi/x86/lpss.c new file mode 100644 index 000000000..0171eef00 --- /dev/null +++ b/drivers/acpi/x86/lpss.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ACPI support for Intel Lynxpoint LPSS. + * + * Copyright (C) 2013, Intel Corporation + * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> + * Rafael J. Wysocki <rafael.j.wysocki@intel.com> + */ + +#include <linux/acpi.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/dmi.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/platform_data/x86/clk-lpss.h> +#include <linux/platform_data/x86/pmc_atom.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/pxa2xx_ssp.h> +#include <linux/suspend.h> +#include <linux/delay.h> + +#include "../internal.h" + +#ifdef CONFIG_X86_INTEL_LPSS + +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> +#include <asm/iosf_mbi.h> + +#define LPSS_ADDR(desc) ((unsigned long)&desc) + +#define LPSS_CLK_SIZE 0x04 +#define LPSS_LTR_SIZE 0x18 + +/* Offsets relative to LPSS_PRIVATE_OFFSET */ +#define LPSS_CLK_DIVIDER_DEF_MASK (BIT(1) | BIT(16)) +#define LPSS_RESETS 0x04 +#define LPSS_RESETS_RESET_FUNC BIT(0) +#define LPSS_RESETS_RESET_APB BIT(1) +#define LPSS_GENERAL 0x08 +#define LPSS_GENERAL_LTR_MODE_SW BIT(2) +#define LPSS_GENERAL_UART_RTS_OVRD BIT(3) +#define LPSS_SW_LTR 0x10 +#define LPSS_AUTO_LTR 0x14 +#define LPSS_LTR_SNOOP_REQ BIT(15) +#define LPSS_LTR_SNOOP_MASK 0x0000FFFF +#define LPSS_LTR_SNOOP_LAT_1US 0x800 +#define LPSS_LTR_SNOOP_LAT_32US 0xC00 +#define LPSS_LTR_SNOOP_LAT_SHIFT 5 +#define LPSS_LTR_SNOOP_LAT_CUTOFF 3000 +#define LPSS_LTR_MAX_VAL 0x3FF +#define LPSS_TX_INT 0x20 +#define LPSS_TX_INT_MASK BIT(1) + +#define LPSS_PRV_REG_COUNT 9 + +/* LPSS Flags */ +#define LPSS_CLK BIT(0) +#define LPSS_CLK_GATE BIT(1) +#define LPSS_CLK_DIVIDER BIT(2) +#define LPSS_LTR BIT(3) +#define LPSS_SAVE_CTX BIT(4) +/* + * For some devices the DSDT AML code for another device turns off the device + * before our suspend handler runs, causing us to read/save all 1-s (0xffffffff) + * as ctx register values. + * Luckily these devices always use the same ctx register values, so we can + * work around this by saving the ctx registers once on activation. + */ +#define LPSS_SAVE_CTX_ONCE BIT(5) +#define LPSS_NO_D3_DELAY BIT(6) + +struct lpss_private_data; + +struct lpss_device_desc { + unsigned int flags; + const char *clk_con_id; + unsigned int prv_offset; + size_t prv_size_override; + const struct property_entry *properties; + void (*setup)(struct lpss_private_data *pdata); + bool resume_from_noirq; +}; + +static const struct lpss_device_desc lpss_dma_desc = { + .flags = LPSS_CLK, +}; + +struct lpss_private_data { + struct acpi_device *adev; + void __iomem *mmio_base; + resource_size_t mmio_size; + unsigned int fixed_clk_rate; + struct clk *clk; + const struct lpss_device_desc *dev_desc; + u32 prv_reg_ctx[LPSS_PRV_REG_COUNT]; +}; + +/* Devices which need to be in D3 before lpss_iosf_enter_d3_state() proceeds */ +static u32 pmc_atom_d3_mask = 0xfe000ffe; + +/* LPSS run time quirks */ +static unsigned int lpss_quirks; + +/* + * LPSS_QUIRK_ALWAYS_POWER_ON: override power state for LPSS DMA device. + * + * The LPSS DMA controller has neither _PS0 nor _PS3 method. Moreover + * it can be powered off automatically whenever the last LPSS device goes down. + * In case of no power any access to the DMA controller will hang the system. + * The behaviour is reproduced on some HP laptops based on Intel BayTrail as + * well as on ASuS T100TA transformer. + * + * This quirk overrides power state of entire LPSS island to keep DMA powered + * on whenever we have at least one other device in use. + */ +#define LPSS_QUIRK_ALWAYS_POWER_ON BIT(0) + +/* UART Component Parameter Register */ +#define LPSS_UART_CPR 0xF4 +#define LPSS_UART_CPR_AFCE BIT(4) + +static void lpss_uart_setup(struct lpss_private_data *pdata) +{ + unsigned int offset; + u32 val; + + offset = pdata->dev_desc->prv_offset + LPSS_TX_INT; + val = readl(pdata->mmio_base + offset); + writel(val | LPSS_TX_INT_MASK, pdata->mmio_base + offset); + + val = readl(pdata->mmio_base + LPSS_UART_CPR); + if (!(val & LPSS_UART_CPR_AFCE)) { + offset = pdata->dev_desc->prv_offset + LPSS_GENERAL; + val = readl(pdata->mmio_base + offset); + val |= LPSS_GENERAL_UART_RTS_OVRD; + writel(val, pdata->mmio_base + offset); + } +} + +static void lpss_deassert_reset(struct lpss_private_data *pdata) +{ + unsigned int offset; + u32 val; + + offset = pdata->dev_desc->prv_offset + LPSS_RESETS; + val = readl(pdata->mmio_base + offset); + val |= LPSS_RESETS_RESET_APB | LPSS_RESETS_RESET_FUNC; + writel(val, pdata->mmio_base + offset); +} + +/* + * BYT PWM used for backlight control by the i915 driver on systems without + * the Crystal Cove PMIC. + */ +static struct pwm_lookup byt_pwm_lookup[] = { + PWM_LOOKUP_WITH_MODULE("80860F09:00", 0, "0000:00:02.0", + "pwm_soc_backlight", 0, PWM_POLARITY_NORMAL, + "pwm-lpss-platform"), +}; + +static void byt_pwm_setup(struct lpss_private_data *pdata) +{ + /* Only call pwm_add_table for the first PWM controller */ + if (acpi_dev_uid_match(pdata->adev, 1)) + pwm_add_table(byt_pwm_lookup, ARRAY_SIZE(byt_pwm_lookup)); +} + +#define LPSS_I2C_ENABLE 0x6c + +static void byt_i2c_setup(struct lpss_private_data *pdata) +{ + acpi_handle handle = pdata->adev->handle; + unsigned long long shared_host = 0; + acpi_status status; + u64 uid; + + /* Expected to always be successful, but better safe then sorry */ + if (!acpi_dev_uid_to_integer(pdata->adev, &uid) && uid) { + /* Detect I2C bus shared with PUNIT and ignore its d3 status */ + status = acpi_evaluate_integer(handle, "_SEM", NULL, &shared_host); + if (ACPI_SUCCESS(status) && shared_host) + pmc_atom_d3_mask &= ~(BIT_LPSS2_F1_I2C1 << (uid - 1)); + } + + lpss_deassert_reset(pdata); + + if (readl(pdata->mmio_base + pdata->dev_desc->prv_offset)) + pdata->fixed_clk_rate = 133000000; + + writel(0, pdata->mmio_base + LPSS_I2C_ENABLE); +} + +/* + * BSW PWM1 is used for backlight control by the i915 driver + * BSW PWM2 is used for backlight control for fixed (etched into the glass) + * touch controls on some models. These touch-controls have specialized + * drivers which know they need the "pwm_soc_lpss_2" con-id. + */ +static struct pwm_lookup bsw_pwm_lookup[] = { + PWM_LOOKUP_WITH_MODULE("80862288:00", 0, "0000:00:02.0", + "pwm_soc_backlight", 0, PWM_POLARITY_NORMAL, + "pwm-lpss-platform"), + PWM_LOOKUP_WITH_MODULE("80862289:00", 0, NULL, + "pwm_soc_lpss_2", 0, PWM_POLARITY_NORMAL, + "pwm-lpss-platform"), +}; + +static void bsw_pwm_setup(struct lpss_private_data *pdata) +{ + /* Only call pwm_add_table for the first PWM controller */ + if (acpi_dev_uid_match(pdata->adev, 1)) + pwm_add_table(bsw_pwm_lookup, ARRAY_SIZE(bsw_pwm_lookup)); +} + +static const struct property_entry lpt_spi_properties[] = { + PROPERTY_ENTRY_U32("intel,spi-pxa2xx-type", LPSS_LPT_SSP), + { } +}; + +static const struct lpss_device_desc lpt_spi_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR + | LPSS_SAVE_CTX, + .prv_offset = 0x800, + .properties = lpt_spi_properties, +}; + +static const struct lpss_device_desc lpt_i2c_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_LTR | LPSS_SAVE_CTX, + .prv_offset = 0x800, +}; + +static struct property_entry uart_properties[] = { + PROPERTY_ENTRY_U32("reg-io-width", 4), + PROPERTY_ENTRY_U32("reg-shift", 2), + PROPERTY_ENTRY_BOOL("snps,uart-16550-compatible"), + { }, +}; + +static const struct lpss_device_desc lpt_uart_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR + | LPSS_SAVE_CTX, + .clk_con_id = "baudclk", + .prv_offset = 0x800, + .setup = lpss_uart_setup, + .properties = uart_properties, +}; + +static const struct lpss_device_desc lpt_sdio_dev_desc = { + .flags = LPSS_LTR, + .prv_offset = 0x1000, + .prv_size_override = 0x1018, +}; + +static const struct lpss_device_desc byt_pwm_dev_desc = { + .flags = LPSS_SAVE_CTX, + .prv_offset = 0x800, + .setup = byt_pwm_setup, +}; + +static const struct lpss_device_desc bsw_pwm_dev_desc = { + .flags = LPSS_SAVE_CTX_ONCE | LPSS_NO_D3_DELAY, + .prv_offset = 0x800, + .setup = bsw_pwm_setup, + .resume_from_noirq = true, +}; + +static const struct lpss_device_desc bsw_pwm2_dev_desc = { + .flags = LPSS_SAVE_CTX_ONCE | LPSS_NO_D3_DELAY, + .prv_offset = 0x800, + .resume_from_noirq = true, +}; + +static const struct lpss_device_desc byt_uart_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX, + .clk_con_id = "baudclk", + .prv_offset = 0x800, + .setup = lpss_uart_setup, + .properties = uart_properties, +}; + +static const struct lpss_device_desc bsw_uart_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX + | LPSS_NO_D3_DELAY, + .clk_con_id = "baudclk", + .prv_offset = 0x800, + .setup = lpss_uart_setup, + .properties = uart_properties, +}; + +static const struct property_entry byt_spi_properties[] = { + PROPERTY_ENTRY_U32("intel,spi-pxa2xx-type", LPSS_BYT_SSP), + { } +}; + +static const struct lpss_device_desc byt_spi_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX, + .prv_offset = 0x400, + .properties = byt_spi_properties, +}; + +static const struct lpss_device_desc byt_sdio_dev_desc = { + .flags = LPSS_CLK, +}; + +static const struct lpss_device_desc byt_i2c_dev_desc = { + .flags = LPSS_CLK | LPSS_SAVE_CTX, + .prv_offset = 0x800, + .setup = byt_i2c_setup, + .resume_from_noirq = true, +}; + +static const struct lpss_device_desc bsw_i2c_dev_desc = { + .flags = LPSS_CLK | LPSS_SAVE_CTX | LPSS_NO_D3_DELAY, + .prv_offset = 0x800, + .setup = byt_i2c_setup, + .resume_from_noirq = true, +}; + +static const struct property_entry bsw_spi_properties[] = { + PROPERTY_ENTRY_U32("intel,spi-pxa2xx-type", LPSS_BSW_SSP), + PROPERTY_ENTRY_U32("num-cs", 2), + { } +}; + +static const struct lpss_device_desc bsw_spi_dev_desc = { + .flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX + | LPSS_NO_D3_DELAY, + .prv_offset = 0x400, + .setup = lpss_deassert_reset, + .properties = bsw_spi_properties, +}; + +static const struct x86_cpu_id lpss_cpu_ids[] = { + X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, NULL), + X86_MATCH_VFM(INTEL_ATOM_AIRMONT, NULL), + {} +}; + +#else + +#define LPSS_ADDR(desc) (0UL) + +#endif /* CONFIG_X86_INTEL_LPSS */ + +static const struct acpi_device_id acpi_lpss_device_ids[] = { + /* Generic LPSS devices */ + { "INTL9C60", LPSS_ADDR(lpss_dma_desc) }, + + /* Lynxpoint LPSS devices */ + { "INT33C0", LPSS_ADDR(lpt_spi_dev_desc) }, + { "INT33C1", LPSS_ADDR(lpt_spi_dev_desc) }, + { "INT33C2", LPSS_ADDR(lpt_i2c_dev_desc) }, + { "INT33C3", LPSS_ADDR(lpt_i2c_dev_desc) }, + { "INT33C4", LPSS_ADDR(lpt_uart_dev_desc) }, + { "INT33C5", LPSS_ADDR(lpt_uart_dev_desc) }, + { "INT33C6", LPSS_ADDR(lpt_sdio_dev_desc) }, + + /* BayTrail LPSS devices */ + { "80860F09", LPSS_ADDR(byt_pwm_dev_desc) }, + { "80860F0A", LPSS_ADDR(byt_uart_dev_desc) }, + { "80860F0E", LPSS_ADDR(byt_spi_dev_desc) }, + { "80860F14", LPSS_ADDR(byt_sdio_dev_desc) }, + { "80860F41", LPSS_ADDR(byt_i2c_dev_desc) }, + + /* Braswell LPSS devices */ + { "80862286", LPSS_ADDR(lpss_dma_desc) }, + { "80862288", LPSS_ADDR(bsw_pwm_dev_desc) }, + { "80862289", LPSS_ADDR(bsw_pwm2_dev_desc) }, + { "8086228A", LPSS_ADDR(bsw_uart_dev_desc) }, + { "8086228E", LPSS_ADDR(bsw_spi_dev_desc) }, + { "808622C0", LPSS_ADDR(lpss_dma_desc) }, + { "808622C1", LPSS_ADDR(bsw_i2c_dev_desc) }, + + /* Broadwell LPSS devices */ + { "INT3430", LPSS_ADDR(lpt_spi_dev_desc) }, + { "INT3431", LPSS_ADDR(lpt_spi_dev_desc) }, + { "INT3432", LPSS_ADDR(lpt_i2c_dev_desc) }, + { "INT3433", LPSS_ADDR(lpt_i2c_dev_desc) }, + { "INT3434", LPSS_ADDR(lpt_uart_dev_desc) }, + { "INT3435", LPSS_ADDR(lpt_uart_dev_desc) }, + { "INT3436", LPSS_ADDR(lpt_sdio_dev_desc) }, + + { } +}; + +#ifdef CONFIG_X86_INTEL_LPSS + +/* LPSS main clock device. */ +static struct platform_device *lpss_clk_dev; + +static inline void lpt_register_clock_device(void) +{ + lpss_clk_dev = platform_device_register_simple("clk-lpss-atom", + PLATFORM_DEVID_NONE, + NULL, 0); +} + +static int register_device_clock(struct acpi_device *adev, + struct lpss_private_data *pdata) +{ + const struct lpss_device_desc *dev_desc = pdata->dev_desc; + const char *devname = dev_name(&adev->dev); + struct clk *clk; + struct lpss_clk_data *clk_data; + const char *parent, *clk_name; + void __iomem *prv_base; + + if (!lpss_clk_dev) + lpt_register_clock_device(); + + if (IS_ERR(lpss_clk_dev)) + return PTR_ERR(lpss_clk_dev); + + clk_data = platform_get_drvdata(lpss_clk_dev); + if (!clk_data) + return -ENODEV; + clk = clk_data->clk; + + if (!pdata->mmio_base + || pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE) + return -ENODATA; + + parent = clk_data->name; + prv_base = pdata->mmio_base + dev_desc->prv_offset; + + if (pdata->fixed_clk_rate) { + clk = clk_register_fixed_rate(NULL, devname, parent, 0, + pdata->fixed_clk_rate); + goto out; + } + + if (dev_desc->flags & LPSS_CLK_GATE) { + clk = clk_register_gate(NULL, devname, parent, 0, + prv_base, 0, 0, NULL); + parent = devname; + } + + if (dev_desc->flags & LPSS_CLK_DIVIDER) { + /* Prevent division by zero */ + if (!readl(prv_base)) + writel(LPSS_CLK_DIVIDER_DEF_MASK, prv_base); + + clk_name = kasprintf(GFP_KERNEL, "%s-div", devname); + if (!clk_name) + return -ENOMEM; + clk = clk_register_fractional_divider(NULL, clk_name, parent, + 0, prv_base, 1, 15, 16, 15, + CLK_FRAC_DIVIDER_POWER_OF_TWO_PS, + NULL); + parent = clk_name; + + clk_name = kasprintf(GFP_KERNEL, "%s-update", devname); + if (!clk_name) { + kfree(parent); + return -ENOMEM; + } + clk = clk_register_gate(NULL, clk_name, parent, + CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, + prv_base, 31, 0, NULL); + kfree(parent); + kfree(clk_name); + } +out: + if (IS_ERR(clk)) + return PTR_ERR(clk); + + pdata->clk = clk; + clk_register_clkdev(clk, dev_desc->clk_con_id, devname); + return 0; +} + +struct lpss_device_links { + const char *supplier_hid; + const char *supplier_uid; + const char *consumer_hid; + const char *consumer_uid; + u32 flags; + const struct dmi_system_id *dep_missing_ids; +}; + +/* Please keep this list sorted alphabetically by vendor and model */ +static const struct dmi_system_id i2c1_dep_missing_dmi_ids[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "T200TA"), + }, + }, + {} +}; + +/* + * The _DEP method is used to identify dependencies but instead of creating + * device links for every handle in _DEP, only links in the following list are + * created. That is necessary because, in the general case, _DEP can refer to + * devices that might not have drivers, or that are on different buses, or where + * the supplier is not enumerated until after the consumer is probed. + */ +static const struct lpss_device_links lpss_device_links[] = { + /* CHT External sdcard slot controller depends on PMIC I2C ctrl */ + {"808622C1", "7", "80860F14", "3", DL_FLAG_PM_RUNTIME}, + /* CHT iGPU depends on PMIC I2C controller */ + {"808622C1", "7", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME}, + /* BYT iGPU depends on the Embedded Controller I2C controller (UID 1) */ + {"80860F41", "1", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME, + i2c1_dep_missing_dmi_ids}, + /* BYT CR iGPU depends on PMIC I2C controller (UID 5 on CR) */ + {"80860F41", "5", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME}, + /* BYT iGPU depends on PMIC I2C controller (UID 7 on non CR) */ + {"80860F41", "7", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME}, +}; + +static bool acpi_lpss_is_supplier(struct acpi_device *adev, + const struct lpss_device_links *link) +{ + return acpi_dev_hid_uid_match(adev, link->supplier_hid, link->supplier_uid); +} + +static bool acpi_lpss_is_consumer(struct acpi_device *adev, + const struct lpss_device_links *link) +{ + return acpi_dev_hid_uid_match(adev, link->consumer_hid, link->consumer_uid); +} + +struct hid_uid { + const char *hid; + const char *uid; +}; + +static int match_hid_uid(struct device *dev, const void *data) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + const struct hid_uid *id = data; + + if (!adev) + return 0; + + return acpi_dev_hid_uid_match(adev, id->hid, id->uid); +} + +static struct device *acpi_lpss_find_device(const char *hid, const char *uid) +{ + struct device *dev; + + struct hid_uid data = { + .hid = hid, + .uid = uid, + }; + + dev = bus_find_device(&platform_bus_type, NULL, &data, match_hid_uid); + if (dev) + return dev; + + return bus_find_device(&pci_bus_type, NULL, &data, match_hid_uid); +} + +static void acpi_lpss_link_consumer(struct device *dev1, + const struct lpss_device_links *link) +{ + struct device *dev2; + + dev2 = acpi_lpss_find_device(link->consumer_hid, link->consumer_uid); + if (!dev2) + return; + + if ((link->dep_missing_ids && dmi_check_system(link->dep_missing_ids)) + || acpi_device_dep(ACPI_HANDLE(dev2), ACPI_HANDLE(dev1))) + device_link_add(dev2, dev1, link->flags); + + put_device(dev2); +} + +static void acpi_lpss_link_supplier(struct device *dev1, + const struct lpss_device_links *link) +{ + struct device *dev2; + + dev2 = acpi_lpss_find_device(link->supplier_hid, link->supplier_uid); + if (!dev2) + return; + + if ((link->dep_missing_ids && dmi_check_system(link->dep_missing_ids)) + || acpi_device_dep(ACPI_HANDLE(dev1), ACPI_HANDLE(dev2))) + device_link_add(dev1, dev2, link->flags); + + put_device(dev2); +} + +static void acpi_lpss_create_device_links(struct acpi_device *adev, + struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(lpss_device_links); i++) { + const struct lpss_device_links *link = &lpss_device_links[i]; + + if (acpi_lpss_is_supplier(adev, link)) + acpi_lpss_link_consumer(&pdev->dev, link); + + if (acpi_lpss_is_consumer(adev, link)) + acpi_lpss_link_supplier(&pdev->dev, link); + } +} + +static int acpi_lpss_create_device(struct acpi_device *adev, + const struct acpi_device_id *id) +{ + const struct lpss_device_desc *dev_desc; + struct lpss_private_data *pdata; + struct resource_entry *rentry; + struct list_head resource_list; + struct platform_device *pdev; + int ret; + + dev_desc = (const struct lpss_device_desc *)id->driver_data; + if (!dev_desc) + return -EINVAL; + + pdata = kzalloc_obj(*pdata); + if (!pdata) + return -ENOMEM; + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_memory_resources(adev, &resource_list); + if (ret < 0) + goto err_out; + + rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node); + if (rentry) { + if (dev_desc->prv_size_override) + pdata->mmio_size = dev_desc->prv_size_override; + else + pdata->mmio_size = resource_size(rentry->res); + pdata->mmio_base = ioremap(rentry->res->start, pdata->mmio_size); + } + + acpi_dev_free_resource_list(&resource_list); + + if (!pdata->mmio_base) { + /* Avoid acpi_bus_attach() instantiating a pdev for this dev. */ + adev->pnp.type.platform_id = 0; + goto out_free; + } + + pdata->adev = adev; + pdata->dev_desc = dev_desc; + + if (dev_desc->setup) + dev_desc->setup(pdata); + + if (dev_desc->flags & LPSS_CLK) { + ret = register_device_clock(adev, pdata); + if (ret) + goto out_free; + } + + /* + * This works around a known issue in ACPI tables where LPSS devices + * have _PS0 and _PS3 without _PSC (and no power resources), so + * acpi_bus_init_power() will assume that the BIOS has put them into D0. + */ + acpi_device_fix_up_power(adev); + + adev->driver_data = pdata; + pdev = acpi_create_platform_device(adev, dev_desc->properties); + if (IS_ERR_OR_NULL(pdev)) { + adev->driver_data = NULL; + ret = PTR_ERR(pdev); + goto err_out; + } + + acpi_lpss_create_device_links(adev, pdev); + return 1; + +out_free: + /* Skip the device, but continue the namespace scan */ + ret = 0; +err_out: + kfree(pdata); + return ret; +} + +static u32 __lpss_reg_read(struct lpss_private_data *pdata, unsigned int reg) +{ + return readl(pdata->mmio_base + pdata->dev_desc->prv_offset + reg); +} + +static void __lpss_reg_write(u32 val, struct lpss_private_data *pdata, + unsigned int reg) +{ + writel(val, pdata->mmio_base + pdata->dev_desc->prv_offset + reg); +} + +static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + struct lpss_private_data *pdata; + unsigned long flags; + int ret; + + if (WARN_ON(!adev)) + return -ENODEV; + + spin_lock_irqsave(&dev->power.lock, flags); + if (pm_runtime_suspended(dev)) { + ret = -EAGAIN; + goto out; + } + pdata = acpi_driver_data(adev); + if (WARN_ON(!pdata || !pdata->mmio_base)) { + ret = -ENODEV; + goto out; + } + *val = __lpss_reg_read(pdata, reg); + ret = 0; + + out: + spin_unlock_irqrestore(&dev->power.lock, flags); + return ret; +} + +static ssize_t lpss_ltr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u32 ltr_value = 0; + unsigned int reg; + int ret; + + reg = strcmp(attr->attr.name, "auto_ltr") ? LPSS_SW_LTR : LPSS_AUTO_LTR; + ret = lpss_reg_read(dev, reg, <r_value); + if (ret) + return ret; + + return sysfs_emit(buf, "%08x\n", ltr_value); +} + +static ssize_t lpss_ltr_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 ltr_mode = 0; + char *outstr; + int ret; + + ret = lpss_reg_read(dev, LPSS_GENERAL, <r_mode); + if (ret) + return ret; + + outstr = (ltr_mode & LPSS_GENERAL_LTR_MODE_SW) ? "sw" : "auto"; + return sprintf(buf, "%s\n", outstr); +} + +static DEVICE_ATTR(auto_ltr, S_IRUSR, lpss_ltr_show, NULL); +static DEVICE_ATTR(sw_ltr, S_IRUSR, lpss_ltr_show, NULL); +static DEVICE_ATTR(ltr_mode, S_IRUSR, lpss_ltr_mode_show, NULL); + +static struct attribute *lpss_attrs[] = { + &dev_attr_auto_ltr.attr, + &dev_attr_sw_ltr.attr, + &dev_attr_ltr_mode.attr, + NULL, +}; + +static const struct attribute_group lpss_attr_group = { + .attrs = lpss_attrs, + .name = "lpss_ltr", +}; + +static void acpi_lpss_set_ltr(struct device *dev, s32 val) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + u32 ltr_mode, ltr_val; + + ltr_mode = __lpss_reg_read(pdata, LPSS_GENERAL); + if (val < 0) { + if (ltr_mode & LPSS_GENERAL_LTR_MODE_SW) { + ltr_mode &= ~LPSS_GENERAL_LTR_MODE_SW; + __lpss_reg_write(ltr_mode, pdata, LPSS_GENERAL); + } + return; + } + ltr_val = __lpss_reg_read(pdata, LPSS_SW_LTR) & ~LPSS_LTR_SNOOP_MASK; + if (val >= LPSS_LTR_SNOOP_LAT_CUTOFF) { + ltr_val |= LPSS_LTR_SNOOP_LAT_32US; + val = LPSS_LTR_MAX_VAL; + } else if (val > LPSS_LTR_MAX_VAL) { + ltr_val |= LPSS_LTR_SNOOP_LAT_32US | LPSS_LTR_SNOOP_REQ; + val >>= LPSS_LTR_SNOOP_LAT_SHIFT; + } else { + ltr_val |= LPSS_LTR_SNOOP_LAT_1US | LPSS_LTR_SNOOP_REQ; + } + ltr_val |= val; + __lpss_reg_write(ltr_val, pdata, LPSS_SW_LTR); + if (!(ltr_mode & LPSS_GENERAL_LTR_MODE_SW)) { + ltr_mode |= LPSS_GENERAL_LTR_MODE_SW; + __lpss_reg_write(ltr_mode, pdata, LPSS_GENERAL); + } +} + +#ifdef CONFIG_PM +/** + * acpi_lpss_save_ctx() - Save the private registers of LPSS device + * @dev: LPSS device + * @pdata: pointer to the private data of the LPSS device + * + * Most LPSS devices have private registers which may loose their context when + * the device is powered down. acpi_lpss_save_ctx() saves those registers into + * prv_reg_ctx array. + */ +static void acpi_lpss_save_ctx(struct device *dev, + struct lpss_private_data *pdata) +{ + unsigned int i; + + for (i = 0; i < LPSS_PRV_REG_COUNT; i++) { + unsigned long offset = i * sizeof(u32); + + pdata->prv_reg_ctx[i] = __lpss_reg_read(pdata, offset); + dev_dbg(dev, "saving 0x%08x from LPSS reg at offset 0x%02lx\n", + pdata->prv_reg_ctx[i], offset); + } +} + +/** + * acpi_lpss_restore_ctx() - Restore the private registers of LPSS device + * @dev: LPSS device + * @pdata: pointer to the private data of the LPSS device + * + * Restores the registers that were previously stored with acpi_lpss_save_ctx(). + */ +static void acpi_lpss_restore_ctx(struct device *dev, + struct lpss_private_data *pdata) +{ + unsigned int i; + + for (i = 0; i < LPSS_PRV_REG_COUNT; i++) { + unsigned long offset = i * sizeof(u32); + + __lpss_reg_write(pdata->prv_reg_ctx[i], pdata, offset); + dev_dbg(dev, "restoring 0x%08x to LPSS reg at offset 0x%02lx\n", + pdata->prv_reg_ctx[i], offset); + } +} + +static void acpi_lpss_d3_to_d0_delay(struct lpss_private_data *pdata) +{ + /* + * The following delay is needed or the subsequent write operations may + * fail. The LPSS devices are actually PCI devices and the PCI spec + * expects 10ms delay before the device can be accessed after D3 to D0 + * transition. However some platforms like BSW does not need this delay. + */ + unsigned int delay = 10; /* default 10ms delay */ + + if (pdata->dev_desc->flags & LPSS_NO_D3_DELAY) + delay = 0; + + msleep(delay); +} + +static int acpi_lpss_activate(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + ret = acpi_dev_resume(dev); + if (ret) + return ret; + + acpi_lpss_d3_to_d0_delay(pdata); + + /* + * This is called only on ->probe() stage where a device is either in + * known state defined by BIOS or most likely powered off. Due to this + * we have to deassert reset line to be sure that ->probe() will + * recognize the device. + */ + if (pdata->dev_desc->flags & (LPSS_SAVE_CTX | LPSS_SAVE_CTX_ONCE)) + lpss_deassert_reset(pdata); + + if (pdata->dev_desc->flags & LPSS_SAVE_CTX_ONCE) + acpi_lpss_save_ctx(dev, pdata); + + return 0; +} + +static void acpi_lpss_dismiss(struct device *dev) +{ + acpi_dev_suspend(dev, false); +} + +/* IOSF SB for LPSS island */ +#define LPSS_IOSF_UNIT_LPIOEP 0xA0 +#define LPSS_IOSF_UNIT_LPIO1 0xAB +#define LPSS_IOSF_UNIT_LPIO2 0xAC + +#define LPSS_IOSF_PMCSR 0x84 +#define LPSS_PMCSR_D0 0 +#define LPSS_PMCSR_D3hot 3 +#define LPSS_PMCSR_Dx_MASK GENMASK(1, 0) + +#define LPSS_IOSF_GPIODEF0 0x154 +#define LPSS_GPIODEF0_DMA1_D3 BIT(2) +#define LPSS_GPIODEF0_DMA2_D3 BIT(3) +#define LPSS_GPIODEF0_DMA_D3_MASK GENMASK(3, 2) +#define LPSS_GPIODEF0_DMA_LLP BIT(13) + +static DEFINE_MUTEX(lpss_iosf_mutex); +static bool lpss_iosf_d3_entered = true; + +static void lpss_iosf_enter_d3_state(void) +{ + u32 value1 = 0; + u32 mask1 = LPSS_GPIODEF0_DMA_D3_MASK | LPSS_GPIODEF0_DMA_LLP; + u32 value2 = LPSS_PMCSR_D3hot; + u32 mask2 = LPSS_PMCSR_Dx_MASK; + /* + * PMC provides an information about actual status of the LPSS devices. + * Here we read the values related to LPSS power island, i.e. LPSS + * devices, excluding both LPSS DMA controllers, along with SCC domain. + */ + u32 func_dis, d3_sts_0, pmc_status; + int ret; + + ret = pmc_atom_read(PMC_FUNC_DIS, &func_dis); + if (ret) + return; + + mutex_lock(&lpss_iosf_mutex); + + ret = pmc_atom_read(PMC_D3_STS_0, &d3_sts_0); + if (ret) + goto exit; + + /* + * Get the status of entire LPSS power island per device basis. + * Shutdown both LPSS DMA controllers if and only if all other devices + * are already in D3hot. + */ + pmc_status = (~(d3_sts_0 | func_dis)) & pmc_atom_d3_mask; + if (pmc_status) + goto exit; + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIO1, MBI_CFG_WRITE, + LPSS_IOSF_PMCSR, value2, mask2); + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIO2, MBI_CFG_WRITE, + LPSS_IOSF_PMCSR, value2, mask2); + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIOEP, MBI_CR_WRITE, + LPSS_IOSF_GPIODEF0, value1, mask1); + + lpss_iosf_d3_entered = true; + +exit: + mutex_unlock(&lpss_iosf_mutex); +} + +static void lpss_iosf_exit_d3_state(void) +{ + u32 value1 = LPSS_GPIODEF0_DMA1_D3 | LPSS_GPIODEF0_DMA2_D3 | + LPSS_GPIODEF0_DMA_LLP; + u32 mask1 = LPSS_GPIODEF0_DMA_D3_MASK | LPSS_GPIODEF0_DMA_LLP; + u32 value2 = LPSS_PMCSR_D0; + u32 mask2 = LPSS_PMCSR_Dx_MASK; + + mutex_lock(&lpss_iosf_mutex); + + if (!lpss_iosf_d3_entered) + goto exit; + + lpss_iosf_d3_entered = false; + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIOEP, MBI_CR_WRITE, + LPSS_IOSF_GPIODEF0, value1, mask1); + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIO2, MBI_CFG_WRITE, + LPSS_IOSF_PMCSR, value2, mask2); + + iosf_mbi_modify(LPSS_IOSF_UNIT_LPIO1, MBI_CFG_WRITE, + LPSS_IOSF_PMCSR, value2, mask2); + +exit: + mutex_unlock(&lpss_iosf_mutex); +} + +static int acpi_lpss_suspend(struct device *dev, bool wakeup) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + if (pdata->dev_desc->flags & LPSS_SAVE_CTX) + acpi_lpss_save_ctx(dev, pdata); + + ret = acpi_dev_suspend(dev, wakeup); + + /* + * This call must be last in the sequence, otherwise PMC will return + * wrong status for devices being about to be powered off. See + * lpss_iosf_enter_d3_state() for further information. + */ + if (acpi_target_system_state() == ACPI_STATE_S0 && + lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available()) + lpss_iosf_enter_d3_state(); + + return ret; +} + +static int acpi_lpss_resume(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + /* + * This call is kept first to be in symmetry with + * acpi_lpss_runtime_suspend() one. + */ + if (lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available()) + lpss_iosf_exit_d3_state(); + + ret = acpi_dev_resume(dev); + if (ret) + return ret; + + acpi_lpss_d3_to_d0_delay(pdata); + + if (pdata->dev_desc->flags & (LPSS_SAVE_CTX | LPSS_SAVE_CTX_ONCE)) + acpi_lpss_restore_ctx(dev, pdata); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int acpi_lpss_do_suspend_late(struct device *dev) +{ + int ret; + + if (dev_pm_skip_suspend(dev)) + return 0; + + ret = pm_generic_suspend_late(dev); + return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev)); +} + +static int acpi_lpss_suspend_late(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (pdata->dev_desc->resume_from_noirq) + return 0; + + return acpi_lpss_do_suspend_late(dev); +} + +static int acpi_lpss_suspend_noirq(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + if (pdata->dev_desc->resume_from_noirq) { + /* + * The driver's ->suspend_late callback will be invoked by + * acpi_lpss_do_suspend_late(), with the assumption that the + * driver really wanted to run that code in ->suspend_noirq, but + * it could not run after acpi_dev_suspend() and the driver + * expected the latter to be called in the "late" phase. + */ + ret = acpi_lpss_do_suspend_late(dev); + if (ret) + return ret; + } + + return acpi_subsys_suspend_noirq(dev); +} + +static int acpi_lpss_do_resume_early(struct device *dev) +{ + int ret = acpi_lpss_resume(dev); + + return ret ? ret : pm_generic_resume_early(dev); +} + +static int acpi_lpss_resume_early(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (pdata->dev_desc->resume_from_noirq) + return 0; + + if (dev_pm_skip_resume(dev)) + return 0; + + return acpi_lpss_do_resume_early(dev); +} + +static int acpi_lpss_resume_noirq(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + /* Follow acpi_subsys_resume_noirq(). */ + if (dev_pm_skip_resume(dev)) + return 0; + + ret = pm_generic_resume_noirq(dev); + if (ret) + return ret; + + if (!pdata->dev_desc->resume_from_noirq) + return 0; + + /* + * The driver's ->resume_early callback will be invoked by + * acpi_lpss_do_resume_early(), with the assumption that the driver + * really wanted to run that code in ->resume_noirq, but it could not + * run before acpi_dev_resume() and the driver expected the latter to be + * called in the "early" phase. + */ + return acpi_lpss_do_resume_early(dev); +} + +static int acpi_lpss_do_restore_early(struct device *dev) +{ + int ret = acpi_lpss_resume(dev); + + return ret ? ret : pm_generic_restore_early(dev); +} + +static int acpi_lpss_restore_early(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (pdata->dev_desc->resume_from_noirq) + return 0; + + return acpi_lpss_do_restore_early(dev); +} + +static int acpi_lpss_restore_noirq(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + int ret; + + ret = pm_generic_restore_noirq(dev); + if (ret) + return ret; + + if (!pdata->dev_desc->resume_from_noirq) + return 0; + + /* This is analogous to what happens in acpi_lpss_resume_noirq(). */ + return acpi_lpss_do_restore_early(dev); +} + +static int acpi_lpss_do_poweroff_late(struct device *dev) +{ + int ret = pm_generic_poweroff_late(dev); + + return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev)); +} + +static int acpi_lpss_poweroff_late(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (dev_pm_skip_suspend(dev)) + return 0; + + if (pdata->dev_desc->resume_from_noirq) + return 0; + + return acpi_lpss_do_poweroff_late(dev); +} + +static int acpi_lpss_poweroff_noirq(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (dev_pm_skip_suspend(dev)) + return 0; + + if (pdata->dev_desc->resume_from_noirq) { + /* This is analogous to the acpi_lpss_suspend_noirq() case. */ + int ret = acpi_lpss_do_poweroff_late(dev); + + if (ret) + return ret; + } + + return pm_generic_poweroff_noirq(dev); +} +#endif /* CONFIG_PM_SLEEP */ + +static int acpi_lpss_runtime_suspend(struct device *dev) +{ + int ret = pm_generic_runtime_suspend(dev); + + return ret ? ret : acpi_lpss_suspend(dev, true); +} + +static int acpi_lpss_runtime_resume(struct device *dev) +{ + int ret = acpi_lpss_resume(dev); + + return ret ? ret : pm_generic_runtime_resume(dev); +} +#endif /* CONFIG_PM */ + +static struct dev_pm_domain acpi_lpss_pm_domain = { +#ifdef CONFIG_PM + .activate = acpi_lpss_activate, + .dismiss = acpi_lpss_dismiss, +#endif + .ops = { +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP + .prepare = acpi_subsys_prepare, + .complete = acpi_subsys_complete, + .suspend = acpi_subsys_suspend, + .suspend_late = acpi_lpss_suspend_late, + .suspend_noirq = acpi_lpss_suspend_noirq, + .resume_noirq = acpi_lpss_resume_noirq, + .resume_early = acpi_lpss_resume_early, + .freeze = acpi_subsys_freeze, + .poweroff = acpi_subsys_poweroff, + .poweroff_late = acpi_lpss_poweroff_late, + .poweroff_noirq = acpi_lpss_poweroff_noirq, + .restore_noirq = acpi_lpss_restore_noirq, + .restore_early = acpi_lpss_restore_early, +#endif + .runtime_suspend = acpi_lpss_runtime_suspend, + .runtime_resume = acpi_lpss_runtime_resume, +#endif + }, +}; + +static int acpi_lpss_platform_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct platform_device *pdev = to_platform_device(data); + struct lpss_private_data *pdata; + struct acpi_device *adev; + const struct acpi_device_id *id; + + id = acpi_match_device(acpi_lpss_device_ids, &pdev->dev); + if (!id || !id->driver_data) + return 0; + + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return 0; + + pdata = acpi_driver_data(adev); + if (!pdata) + return 0; + + if (pdata->mmio_base && + pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) { + dev_err(&pdev->dev, "MMIO size insufficient to access LTR\n"); + return 0; + } + + switch (action) { + case BUS_NOTIFY_BIND_DRIVER: + dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain); + break; + case BUS_NOTIFY_DRIVER_NOT_BOUND: + case BUS_NOTIFY_UNBOUND_DRIVER: + dev_pm_domain_set(&pdev->dev, NULL); + break; + case BUS_NOTIFY_ADD_DEVICE: + dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain); + if (pdata->dev_desc->flags & LPSS_LTR) + return sysfs_create_group(&pdev->dev.kobj, + &lpss_attr_group); + break; + case BUS_NOTIFY_DEL_DEVICE: + if (pdata->dev_desc->flags & LPSS_LTR) + sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group); + dev_pm_domain_set(&pdev->dev, NULL); + break; + default: + break; + } + + return 0; +} + +static struct notifier_block acpi_lpss_nb = { + .notifier_call = acpi_lpss_platform_notify, +}; + +static void acpi_lpss_bind(struct device *dev) +{ + struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev)); + + if (!pdata || !pdata->mmio_base || !(pdata->dev_desc->flags & LPSS_LTR)) + return; + + if (pdata->mmio_size >= pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) + dev->power.set_latency_tolerance = acpi_lpss_set_ltr; + else + dev_err(dev, "MMIO size insufficient to access LTR\n"); +} + +static void acpi_lpss_unbind(struct device *dev) +{ + dev->power.set_latency_tolerance = NULL; +} + +static struct acpi_scan_handler lpss_handler = { + .ids = acpi_lpss_device_ids, + .attach = acpi_lpss_create_device, + .bind = acpi_lpss_bind, + .unbind = acpi_lpss_unbind, +}; + +void __init acpi_lpss_init(void) +{ + const struct x86_cpu_id *id; + int ret; + + ret = lpss_atom_clk_init(); + if (ret) + return; + + id = x86_match_cpu(lpss_cpu_ids); + if (id) + lpss_quirks |= LPSS_QUIRK_ALWAYS_POWER_ON; + + bus_register_notifier(&platform_bus_type, &acpi_lpss_nb); + acpi_scan_add_handler(&lpss_handler); +} + +#else + +static struct acpi_scan_handler lpss_handler = { + .ids = acpi_lpss_device_ids, +}; + +void __init acpi_lpss_init(void) +{ + acpi_scan_add_handler(&lpss_handler); +} + +#endif /* CONFIG_X86_INTEL_LPSS */ diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c new file mode 100644 index 000000000..b6b1dd76a --- /dev/null +++ b/drivers/acpi/x86/s2idle.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Architecture-specific ACPI-based support for suspend-to-idle. + * + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> + * + * On platforms supporting the Low Power S0 Idle interface there is an ACPI + * device object with the PNP0D80 compatible device ID (System Power Management + * Controller) and a specific _DSM method under it. That method, if present, + * can be used to indicate to the platform that the OS is transitioning into a + * low-power state in which certain types of activity are not desirable or that + * it is leaving such a state, which allows the platform to adjust its operation + * mode accordingly. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/suspend.h> + +#include "../sleep.h" + +#ifdef CONFIG_SUSPEND + +static bool sleep_no_lps0 __read_mostly; +module_param(sleep_no_lps0, bool, 0644); +MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); + +static bool check_lps0_constraints __read_mostly; +module_param(check_lps0_constraints, bool, 0644); +MODULE_PARM_DESC(check_lps0_constraints, "Check LPS0 device constraints"); + +static const struct acpi_device_id lps0_device_ids[] = { + {"PNP0D80", }, + {"", }, +}; + +/* Microsoft platform agnostic UUID */ +#define ACPI_LPS0_DSM_UUID_MICROSOFT "11e00d56-ce64-47ce-837b-1f898f9aa461" + +#define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" + +#define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1 +#define ACPI_LPS0_SCREEN_OFF 3 +#define ACPI_LPS0_SCREEN_ON 4 +#define ACPI_LPS0_ENTRY 5 +#define ACPI_LPS0_EXIT 6 +#define ACPI_LPS0_MS_ENTRY 7 +#define ACPI_LPS0_MS_EXIT 8 +#define ACPI_MS_TURN_ON_DISPLAY 9 + +/* AMD */ +#define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" +#define ACPI_LPS0_ENTRY_AMD 2 +#define ACPI_LPS0_EXIT_AMD 3 +#define ACPI_LPS0_SCREEN_OFF_AMD 4 +#define ACPI_LPS0_SCREEN_ON_AMD 5 + +static acpi_handle lps0_device_handle; +static guid_t lps0_dsm_guid; +static int lps0_dsm_func_mask; + +static guid_t lps0_dsm_guid_microsoft; +static int lps0_dsm_func_mask_microsoft; +static int lps0_dsm_state; + +/* Device constraint entry structure */ +struct lpi_device_info { + char *name; + int enabled; + union acpi_object *package; +}; + +/* Constraint package structure */ +struct lpi_device_constraint { + int uid; + int min_dstate; + int function_states; +}; + +struct lpi_constraints { + acpi_handle handle; + int min_dstate; +}; + +/* AMD Constraint package structure */ +struct lpi_device_constraint_amd { + char *name; + int enabled; + int function_states; + int min_dstate; +}; + +static LIST_HEAD(lps0_s2idle_devops_head); + +static struct lpi_constraints *lpi_constraints_table; +static int lpi_constraints_table_size; +static int rev_id; + +#define for_each_lpi_constraint(entry) \ + for (int i = 0; \ + entry = &lpi_constraints_table[i], i < lpi_constraints_table_size; \ + i++) + +static void lpi_device_get_constraints_amd(void) +{ + union acpi_object *out_obj; + int i, j, k; + + out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, + rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, + NULL, ACPI_TYPE_PACKAGE); + + acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n", + out_obj ? "successful" : "failed"); + + if (!out_obj) + return; + + for (i = 0; i < out_obj->package.count; i++) { + union acpi_object *package = &out_obj->package.elements[i]; + + if (package->type == ACPI_TYPE_PACKAGE) { + if (lpi_constraints_table) { + acpi_handle_err(lps0_device_handle, + "Duplicate constraints list\n"); + goto free_acpi_buffer; + } + + lpi_constraints_table = kzalloc_objs(*lpi_constraints_table, + package->package.count); + + if (!lpi_constraints_table) + goto free_acpi_buffer; + + acpi_handle_debug(lps0_device_handle, + "LPI: constraints list begin:\n"); + + for (j = 0; j < package->package.count; j++) { + union acpi_object *info_obj = &package->package.elements[j]; + struct lpi_device_constraint_amd dev_info = {}; + struct lpi_constraints *list; + acpi_status status; + + list = &lpi_constraints_table[lpi_constraints_table_size]; + + for (k = 0; k < info_obj->package.count; k++) { + union acpi_object *obj = &info_obj->package.elements[k]; + + switch (k) { + case 0: + dev_info.enabled = obj->integer.value; + break; + case 1: + dev_info.name = obj->string.pointer; + break; + case 2: + dev_info.function_states = obj->integer.value; + break; + case 3: + dev_info.min_dstate = obj->integer.value; + break; + } + } + + acpi_handle_debug(lps0_device_handle, + "Name:%s, Enabled: %d, States: %d, MinDstate: %d\n", + dev_info.name, + dev_info.enabled, + dev_info.function_states, + dev_info.min_dstate); + + if (!dev_info.enabled || !dev_info.name || + !dev_info.min_dstate) + continue; + + status = acpi_get_handle(NULL, dev_info.name, &list->handle); + if (ACPI_FAILURE(status)) + continue; + + list->min_dstate = dev_info.min_dstate; + + lpi_constraints_table_size++; + } + } + } + + acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n"); + +free_acpi_buffer: + ACPI_FREE(out_obj); +} + +static void lpi_device_get_constraints(void) +{ + union acpi_object *out_obj; + int i; + + out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, + 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, + NULL, ACPI_TYPE_PACKAGE); + + acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n", + out_obj ? "successful" : "failed"); + + if (!out_obj) + return; + + lpi_constraints_table = kzalloc_objs(*lpi_constraints_table, + out_obj->package.count); + if (!lpi_constraints_table) + goto free_acpi_buffer; + + acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n"); + + for (i = 0; i < out_obj->package.count; i++) { + struct lpi_constraints *constraint; + acpi_status status; + union acpi_object *package = &out_obj->package.elements[i]; + struct lpi_device_info info = { }; + int package_count = 0, j; + + if (!package) + continue; + + for (j = 0; j < package->package.count; j++) { + union acpi_object *element = + &(package->package.elements[j]); + + switch (element->type) { + case ACPI_TYPE_INTEGER: + info.enabled = element->integer.value; + break; + case ACPI_TYPE_STRING: + info.name = element->string.pointer; + break; + case ACPI_TYPE_PACKAGE: + package_count = element->package.count; + info.package = element->package.elements; + break; + } + } + + if (!info.enabled || !info.package || !info.name) + continue; + + constraint = &lpi_constraints_table[lpi_constraints_table_size]; + + status = acpi_get_handle(NULL, info.name, &constraint->handle); + if (ACPI_FAILURE(status)) + continue; + + acpi_handle_debug(lps0_device_handle, + "index:%d Name:%s\n", i, info.name); + + constraint->min_dstate = -1; + + for (j = 0; j < package_count; j++) { + union acpi_object *info_obj = &info.package[j]; + union acpi_object *cnstr_pkg; + union acpi_object *obj; + struct lpi_device_constraint dev_info; + + switch (info_obj->type) { + case ACPI_TYPE_INTEGER: + /* version */ + break; + case ACPI_TYPE_PACKAGE: + if (info_obj->package.count < 2) + break; + + cnstr_pkg = info_obj->package.elements; + obj = &cnstr_pkg[0]; + dev_info.uid = obj->integer.value; + obj = &cnstr_pkg[1]; + dev_info.min_dstate = obj->integer.value; + + acpi_handle_debug(lps0_device_handle, + "uid:%d min_dstate:%s\n", + dev_info.uid, + acpi_power_state_string(dev_info.min_dstate)); + + constraint->min_dstate = dev_info.min_dstate; + break; + } + } + + if (constraint->min_dstate < 0) { + acpi_handle_debug(lps0_device_handle, + "Incomplete constraint defined\n"); + continue; + } + + lpi_constraints_table_size++; + } + + acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n"); + +free_acpi_buffer: + ACPI_FREE(out_obj); +} + +static void lpi_check_constraints(void) +{ + struct lpi_constraints *entry; + + if (IS_ERR_OR_NULL(lpi_constraints_table)) + return; + + for_each_lpi_constraint(entry) { + struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle); + + if (!adev) + continue; + + acpi_handle_debug(entry->handle, + "LPI: required min power state:%s current power state:%s\n", + acpi_power_state_string(entry->min_dstate), + acpi_power_state_string(adev->power.state)); + + if (!adev->flags.power_manageable) { + acpi_handle_info(entry->handle, "LPI: Device not power manageable\n"); + entry->handle = NULL; + continue; + } + + if (adev->power.state < entry->min_dstate) + acpi_handle_info(entry->handle, + "LPI: Constraint not met; min power state:%s current power state:%s\n", + acpi_power_state_string(entry->min_dstate), + acpi_power_state_string(adev->power.state)); + } +} + +static bool acpi_s2idle_vendor_amd(void) +{ + return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; +} + +static const char *acpi_sleep_dsm_state_to_str(unsigned int state) +{ + if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) { + switch (state) { + case ACPI_LPS0_SCREEN_OFF: + return "screen off"; + case ACPI_LPS0_SCREEN_ON: + return "screen on"; + case ACPI_LPS0_ENTRY: + return "lps0 entry"; + case ACPI_LPS0_EXIT: + return "lps0 exit"; + case ACPI_LPS0_MS_ENTRY: + return "lps0 ms entry"; + case ACPI_LPS0_MS_EXIT: + return "lps0 ms exit"; + case ACPI_MS_TURN_ON_DISPLAY: + return "lps0 ms turn on display"; + } + } else { + switch (state) { + case ACPI_LPS0_SCREEN_ON_AMD: + return "screen on"; + case ACPI_LPS0_SCREEN_OFF_AMD: + return "screen off"; + case ACPI_LPS0_ENTRY_AMD: + return "lps0 entry"; + case ACPI_LPS0_EXIT_AMD: + return "lps0 exit"; + } + } + + return "unknown"; +} + +static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid) +{ + union acpi_object *out_obj; + + if (!(func_mask & (1 << func))) + return; + + out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid, + rev_id, func, NULL); + ACPI_FREE(out_obj); + + lps0_dsm_state = func; + if (pm_debug_messages_on) { + acpi_handle_info(lps0_device_handle, + "%s transitioned to state %s\n", + out_obj ? "Successfully" : "Failed to", + acpi_sleep_dsm_state_to_str(lps0_dsm_state)); + } +} + + +static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid) +{ + union acpi_object *obj; + int ret = -EINVAL; + + guid_parse(uuid, dsm_guid); + + /* Check if the _DSM is present and as expected. */ + obj = acpi_evaluate_dsm_typed(handle, dsm_guid, rev, 0, NULL, ACPI_TYPE_BUFFER); + if (!obj || obj->buffer.length == 0 || obj->buffer.length > sizeof(u32)) { + acpi_handle_debug(handle, + "_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev); + goto out; + } + + ret = *(int *)obj->buffer.pointer; + acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret); + +out: + ACPI_FREE(obj); + return ret; +} + +struct amd_lps0_hid_device_data { + const bool check_off_by_one; +}; + +static const struct amd_lps0_hid_device_data amd_picasso = { + .check_off_by_one = true, +}; + +static const struct amd_lps0_hid_device_data amd_cezanne = { + .check_off_by_one = false, +}; + +static const struct acpi_device_id amd_hid_ids[] = { + {"AMD0004", (kernel_ulong_t)&amd_picasso, }, + {"AMD0005", (kernel_ulong_t)&amd_picasso, }, + {"AMDI0005", (kernel_ulong_t)&amd_picasso, }, + {"AMDI0006", (kernel_ulong_t)&amd_cezanne, }, + {} +}; + +static int lps0_device_attach(struct acpi_device *adev, + const struct acpi_device_id *not_used) +{ + if (lps0_device_handle) + return 0; + + lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID_MICROSOFT, 0, + &lps0_dsm_guid_microsoft); + if (acpi_s2idle_vendor_amd()) { + static const struct acpi_device_id *dev_id; + const struct amd_lps0_hid_device_data *data; + + for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++) + if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL)) + break; + if (dev_id->id[0]) + data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data; + else + data = &amd_cezanne; + lps0_dsm_func_mask = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid); + if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) { + lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1; + acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n", + ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask); + } + } else { + rev_id = 1; + lps0_dsm_func_mask = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid); + if (lps0_dsm_func_mask > 0 && lps0_dsm_func_mask_microsoft > 0) { + unsigned int func_mask; + + /* + * Log a message if the _DSM function sets for two + * different UUIDs overlap. + */ + func_mask = lps0_dsm_func_mask & lps0_dsm_func_mask_microsoft; + if (func_mask) + acpi_handle_info(adev->handle, + "Duplicate LPS0 _DSM functions (mask: 0x%x)\n", + func_mask); + } + } + + if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0) + return 0; //function evaluation failed + + lps0_device_handle = adev->handle; + + /* + * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in + * the FADT and the default suspend mode was not set from the command + * line. + */ + if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) && + mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) { + mem_sleep_current = PM_SUSPEND_TO_IDLE; + pr_info("Low-power S0 idle used by default for system suspend\n"); + } + + /* + * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the + * EC GPE to be enabled while suspended for certain wakeup devices to + * work, so mark it as wakeup-capable. + */ + acpi_ec_mark_gpe_for_wake(); + + return 0; +} + +static struct acpi_scan_handler lps0_handler = { + .ids = lps0_device_ids, + .attach = lps0_device_attach, +}; + +static int acpi_s2idle_begin_lps0(void) +{ + if (lps0_device_handle && !sleep_no_lps0 && check_lps0_constraints && + !lpi_constraints_table) { + if (acpi_s2idle_vendor_amd()) + lpi_device_get_constraints_amd(); + else + lpi_device_get_constraints(); + + /* + * Try to retrieve the constraints only once because failures + * to do so usually are sticky. + */ + if (!lpi_constraints_table) + lpi_constraints_table = ERR_PTR(-ENODATA); + } + + return acpi_s2idle_begin(); +} + +static int acpi_s2idle_prepare_late_lps0(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return 0; + + if (check_lps0_constraints) + lpi_check_constraints(); + + /* Screen off */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_OFF_AMD : + ACPI_LPS0_SCREEN_OFF, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + + /* LPS0 entry */ + if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) { + /* Modern Standby entry */ + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + } + + if (lps0_dsm_func_mask > 0 && !acpi_s2idle_vendor_amd()) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, + lps0_dsm_func_mask, lps0_dsm_guid); + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->prepare) + handler->prepare(); + } + + return 0; +} + +static void acpi_s2idle_check_lps0(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return; + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->check) + handler->check(); + } +} + +static void acpi_s2idle_restore_early_lps0(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return; + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) + if (handler->restore) + handler->restore(); + + /* LPS0 exit */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_EXIT_AMD : + ACPI_LPS0_EXIT, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) { + acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + /* Intent to turn on display */ + acpi_sleep_run_lps0_dsm(ACPI_MS_TURN_ON_DISPLAY, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + /* Modern Standby exit */ + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + } + + /* Screen on */ + if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_ON_AMD : + ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask, lps0_dsm_guid); +} + +static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { + .begin = acpi_s2idle_begin_lps0, + .prepare = acpi_s2idle_prepare, + .prepare_late = acpi_s2idle_prepare_late_lps0, + .check = acpi_s2idle_check_lps0, + .wake = acpi_s2idle_wake, + .restore_early = acpi_s2idle_restore_early_lps0, + .restore = acpi_s2idle_restore, + .end = acpi_s2idle_end, +}; + +void __init acpi_s2idle_setup(void) +{ + acpi_scan_add_handler(&lps0_handler); + s2idle_set_ops(&acpi_s2idle_ops_lps0); +} + +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + unsigned int sleep_flags; + + if (!lps0_device_handle || sleep_no_lps0) + return -ENODEV; + + sleep_flags = lock_system_sleep(); + list_add(&arg->list_node, &lps0_s2idle_devops_head); + unlock_system_sleep(sleep_flags); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); + +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + unsigned int sleep_flags; + + if (!lps0_device_handle || sleep_no_lps0) + return; + + sleep_flags = lock_system_sleep(); + list_del(&arg->list_node); + unlock_system_sleep(sleep_flags); +} +EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); + +#endif /* CONFIG_SUSPEND */ diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c new file mode 100644 index 000000000..418951639 --- /dev/null +++ b/drivers/acpi/x86/utils.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * X86 ACPI Utility Functions + * + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> + * + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. + */ + +#define pr_fmt(fmt) "ACPI: " fmt + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> +#include "../internal.h" + +/* + * Some ACPI devices are hidden (status == 0x0) in recent BIOS-es because + * some recent Windows drivers bind to one device but poke at multiple + * devices at the same time, so the others get hidden. + * + * Some BIOS-es (temporarily) hide specific APCI devices to work around Windows + * driver bugs. We use DMI matching to match known cases of this. + * + * Likewise sometimes some not-actually present devices are sometimes + * reported as present, which may cause issues. + * + * We work around this by using the below quirk list to override the status + * reported by the _STA method with a fixed value (ACPI_STA_DEFAULT or 0). + * Note this MUST only be done for devices where this is safe. + * + * This status overriding is limited to specific CPU (SoC) models both to + * avoid potentially causing trouble on other models and because some HIDs + * are re-used on different SoCs for completely different devices. + */ +struct override_status_id { + struct acpi_device_id hid[2]; + struct x86_cpu_id cpu_ids[2]; + struct dmi_system_id dmi_ids[2]; /* Optional */ + const char *uid; + const char *path; + unsigned long long status; +}; + +#define ENTRY(status, hid, uid, path, cpu_vfm, dmi...) { \ + { { hid, }, {} }, \ + { X86_MATCH_VFM(cpu_vfm, NULL), {} }, \ + { { .matches = dmi }, {} }, \ + uid, \ + path, \ + status, \ +} + +#define PRESENT_ENTRY_HID(hid, uid, cpu_vfm, dmi...) \ + ENTRY(ACPI_STA_DEFAULT, hid, uid, NULL, cpu_vfm, dmi) + +#define NOT_PRESENT_ENTRY_HID(hid, uid, cpu_vfm, dmi...) \ + ENTRY(0, hid, uid, NULL, cpu_vfm, dmi) + +#define PRESENT_ENTRY_PATH(path, cpu_vfm, dmi...) \ + ENTRY(ACPI_STA_DEFAULT, "", NULL, path, cpu_vfm, dmi) + +#define NOT_PRESENT_ENTRY_PATH(path, cpu_vfm, dmi...) \ + ENTRY(0, "", NULL, path, cpu_vfm, dmi) + +static const struct override_status_id override_status_ids[] = { + /* + * Bay / Cherry Trail PWM directly poked by GPU driver in win10, + * but Linux uses a separate PWM driver, harmless if not used. + */ + PRESENT_ENTRY_HID("80860F09", "1", INTEL_ATOM_SILVERMONT, {}), + PRESENT_ENTRY_HID("80862288", "1", INTEL_ATOM_AIRMONT, {}), + + /* The Xiaomi Mi Pad 2 uses PWM2 for touchkeys backlight control */ + PRESENT_ENTRY_HID("80862289", "2", INTEL_ATOM_AIRMONT, { + DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"), + DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"), + }), + + /* + * Lenovo Yoga Book uses PWM2 for touch keyboard backlight control. + * It needs to be enabled only for the Android device version (YB1-X90* + * aka YETI-11); the Windows version (YB1-X91*) uses ACPI control + * methods. + */ + PRESENT_ENTRY_HID("80862289", "2", INTEL_ATOM_AIRMONT, { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"), + }), + + /* + * The INT0002 device is necessary to clear wakeup interrupt sources + * on Cherry Trail devices, without it we get nobody cared IRQ msgs. + */ + PRESENT_ENTRY_HID("INT0002", "1", INTEL_ATOM_AIRMONT, {}), + /* + * On the Dell Venue 11 Pro 7130 and 7139, the DSDT hides + * the touchscreen ACPI device until a certain time + * after _SB.PCI0.GFX0.LCD.LCD1._ON gets called has passed + * *and* _STA has been called at least 3 times since. + */ + PRESENT_ENTRY_HID("SYNA7500", "1", INTEL_HASWELL_L, { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"), + }), + PRESENT_ENTRY_HID("SYNA7500", "1", INTEL_HASWELL_L, { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7139"), + }), + + /* + * The Dell XPS 15 9550 has a SMO8110 accelerometer / + * HDD freefall sensor which is wrongly marked as not present. + */ + PRESENT_ENTRY_HID("SMO8810", "1", INTEL_SKYLAKE, { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9550"), + }), + + /* + * The GPD win BIOS dated 20170221 has disabled the accelerometer, the + * drivers sometimes cause crashes under Windows and this is how the + * manufacturer has solved this :| The DMI match may not seem unique, + * but it is. In the 67000+ DMI decode dumps from linux-hardware.org + * only 116 have board_vendor set to "AMI Corporation" and of those 116 + * only the GPD win and pocket entries' board_name is "Default string". + * + * Unfortunately the GPD pocket also uses these strings and its BIOS + * was copy-pasted from the GPD win, so it has a disabled KIOX000A + * node which we should not enable, thus we also check the BIOS date. + */ + PRESENT_ENTRY_HID("KIOX000A", "1", INTEL_ATOM_AIRMONT, { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), + DMI_MATCH(DMI_BIOS_DATE, "02/21/2017") + }), + PRESENT_ENTRY_HID("KIOX000A", "1", INTEL_ATOM_AIRMONT, { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), + DMI_MATCH(DMI_BIOS_DATE, "03/20/2017") + }), + PRESENT_ENTRY_HID("KIOX000A", "1", INTEL_ATOM_AIRMONT, { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), + DMI_MATCH(DMI_BIOS_DATE, "05/25/2017") + }), + + /* + * The GPD win/pocket have a PCI wifi card, but its DSDT has the SDIO + * mmc controller enabled and that has a child-device which _PS3 + * method sets a GPIO causing the PCI wifi card to turn off. + * See above remark about uniqueness of the DMI match. + */ + NOT_PRESENT_ENTRY_PATH("\\_SB_.PCI0.SDHB.BRC1", INTEL_ATOM_AIRMONT, { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), + }), + + /* + * The LSM303D on the Lenovo Yoga Tablet 2 series is present + * as both ACCL0001 and MAGN0001. As we can only ever register an + * i2c client for one of them, ignore MAGN0001. + */ + NOT_PRESENT_ENTRY_HID("MAGN0001", "1", INTEL_ATOM_SILVERMONT, { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "YOGATablet2"), + }), +}; + +bool acpi_device_override_status(struct acpi_device *adev, unsigned long long *status) +{ + bool ret = false; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(override_status_ids); i++) { + if (!x86_match_cpu(override_status_ids[i].cpu_ids)) + continue; + + if (override_status_ids[i].dmi_ids[0].matches[0].slot && + !dmi_check_system(override_status_ids[i].dmi_ids)) + continue; + + if (override_status_ids[i].path) { + struct acpi_buffer path = { ACPI_ALLOCATE_BUFFER, NULL }; + bool match; + + if (acpi_get_name(adev->handle, ACPI_FULL_PATHNAME, &path)) + continue; + + match = strcmp((char *)path.pointer, override_status_ids[i].path) == 0; + kfree(path.pointer); + + if (!match) + continue; + } else { + if (acpi_match_device_ids(adev, override_status_ids[i].hid)) + continue; + + if (!acpi_dev_uid_match(adev, override_status_ids[i].uid)) + continue; + } + + *status = override_status_ids[i].status; + ret = true; + break; + } + + return ret; +} + +/* + * AMD systems from Renoir onwards *require* that the NVME controller + * is put into D3 over a Modern Standby / suspend-to-idle cycle. + * + * This is "typically" accomplished using the `StorageD3Enable` + * property in the _DSD that is checked via the `acpi_storage_d3` function + * but some OEM systems still don't have it in their BIOS. + * + * The Microsoft documentation for StorageD3Enable mentioned that Windows has + * a hardcoded allowlist for D3 support as well as a registry key to override + * the BIOS, which has been used for these cases. + * + * This allows quirking on Linux in a similar fashion. + * + * Cezanne systems shouldn't *normally* need this as the BIOS includes + * StorageD3Enable. But for two reasons we have added it. + * 1) The BIOS on a number of Dell systems have ambiguity + * between the same value used for _ADR on ACPI nodes GPP1.DEV0 and GPP1.NVME. + * GPP1.NVME is needed to get StorageD3Enable node set properly. + * https://bugzilla.kernel.org/show_bug.cgi?id=216440 + * https://bugzilla.kernel.org/show_bug.cgi?id=216773 + * https://bugzilla.kernel.org/show_bug.cgi?id=217003 + * 2) On at least one HP system StorageD3Enable is missing on the second NVME + * disk in the system. + * 3) On at least one HP Rembrandt system StorageD3Enable is missing on the only + * NVME device. + */ +bool force_storage_d3(void) +{ + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) + return false; + return acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0; +} + +/* + * x86 ACPI boards which ship with only Android as their factory image usually + * declare a whole bunch of bogus I2C devices in their ACPI tables and sometimes + * there are issues with serdev devices on these boards too, e.g. the resource + * points to the wrong serdev_controller. + * + * Instantiating I2C / serdev devs for these bogus devs causes various issues, + * e.g. GPIO/IRQ resource conflicts because sometimes drivers do bind to them. + * The Android x86 kernel fork shipped on these devices has some special code + * to remove the bogus I2C clients (and AFAICT serdevs are ignored completely). + * + * The acpi_quirk_skip_*_enumeration() functions below are used by the I2C or + * serdev code to skip instantiating any I2C or serdev devs on broken boards. + * + * In case of I2C an exception is made for HIDs on the i2c_acpi_known_good_ids + * list. These are known to always be correct (and in case of the audio-codecs + * the drivers heavily rely on the codec being enumerated through ACPI). + * + * Note these boards typically do actually have I2C and serdev devices, + * just different ones then the ones described in their DSDT. The devices + * which are actually present are manually instantiated by the + * drivers/platform/x86/x86-android-tablets.c kernel module. + */ +#define ACPI_QUIRK_SKIP_I2C_CLIENTS BIT(0) +#define ACPI_QUIRK_UART1_SKIP BIT(1) +#define ACPI_QUIRK_UART1_TTY_UART2_SKIP BIT(2) +#define ACPI_QUIRK_PNP_UART1_SKIP BIT(3) +#define ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY BIT(4) +#define ACPI_QUIRK_USE_ACPI_AC_AND_BATTERY BIT(5) +#define ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS BIT(6) + +static const struct dmi_system_id acpi_quirk_skip_dmi_ids[] = { + /* + * 1. Devices with only the skip / don't-skip AC and battery quirks, + * sorted alphabetically. + */ + { + /* ECS EF20EA, AXP288 PMIC but uses separate fuel-gauge */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "EF20EA"), + }, + .driver_data = (void *)ACPI_QUIRK_USE_ACPI_AC_AND_BATTERY + }, + { + /* Lenovo Ideapad Miix 320, AXP288 PMIC, separate fuel-gauge */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "80XF"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), + }, + .driver_data = (void *)ACPI_QUIRK_USE_ACPI_AC_AND_BATTERY + }, + + /* + * 2. Devices which also have the skip i2c/serdev quirks and which + * need the x86-android-tablets module to properly work. + * Sorted alphabetically. + */ +#if IS_ENABLED(CONFIG_X86_ANDROID_TABLETS) + { + /* Acer Iconia One 7 B1-750 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "VESPA2"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Acer Iconia One 8 A1-840 (non FHD version) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"), + /* Above strings are too generic also match BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Asus ME176C tablet */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ME176C"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_UART1_TTY_UART2_SKIP | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Asus TF103C transformer 2-in-1 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Lenovo Yoga Book X90F/L */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_UART1_SKIP | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Lenovo Yoga Tablet 2 1050F/L */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), + DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), + /* Partial match on beginning of BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_PNP_UART1_SKIP | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY), + }, + { + /* Lenovo Yoga Tab 3 Pro X90F */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Medion Lifetab S10346 */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Way too generic, also match on BIOS data */ + DMI_MATCH(DMI_BIOS_DATE, "10/22/2015"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY), + }, + { + /* Nextbook Ares 8 (BYT version)*/ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Nextbook Ares 8A (CHT version)*/ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"), + DMI_MATCH(DMI_BIOS_VERSION, "M882"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY), + }, + { + /* Vexia Edu Atla 10 tablet 5V version */ + .matches = { + /* Having all 3 of these not set is somewhat unique */ + DMI_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), + DMI_MATCH(DMI_PRODUCT_NAME, "To be filled by O.E.M."), + DMI_MATCH(DMI_BOARD_NAME, "To be filled by O.E.M."), + /* Above strings are too generic, also match on BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "05/14/2015"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY), + }, + { + /* Vexia Edu Atla 10 tablet 9V version */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "08/25/2014"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_UART1_SKIP | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY | + ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS), + }, + { + /* Whitelabel (sold as various brands) TM800A550L */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"), + }, + .driver_data = (void *)(ACPI_QUIRK_SKIP_I2C_CLIENTS | + ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY), + }, +#endif + {} +}; + +#if IS_ENABLED(CONFIG_X86_ANDROID_TABLETS) +static const struct acpi_device_id i2c_acpi_known_good_ids[] = { + { "10EC5640", 0 }, /* RealTek ALC5640 audio codec */ + { "10EC5651", 0 }, /* RealTek ALC5651 audio codec */ + { "INT33F4", 0 }, /* X-Powers AXP288 PMIC */ + { "INT33F5", 0 }, /* TI Dollar Cove PMIC */ + { "INT33FD", 0 }, /* Intel Crystal Cove PMIC */ + { "INT34D3", 0 }, /* Intel Whiskey Cove PMIC */ + { "NPCE69A", 0 }, /* Asus Transformer keyboard dock */ + {} +}; + +bool acpi_quirk_skip_i2c_client_enumeration(struct acpi_device *adev) +{ + const struct dmi_system_id *dmi_id; + long quirks; + + dmi_id = dmi_first_match(acpi_quirk_skip_dmi_ids); + if (!dmi_id) + return false; + + quirks = (unsigned long)dmi_id->driver_data; + if (!(quirks & ACPI_QUIRK_SKIP_I2C_CLIENTS)) + return false; + + return acpi_match_device_ids(adev, i2c_acpi_known_good_ids); +} +EXPORT_SYMBOL_GPL(acpi_quirk_skip_i2c_client_enumeration); + +static int acpi_dmi_skip_serdev_enumeration(struct device *controller_parent, bool *skip) +{ + struct acpi_device *adev = ACPI_COMPANION(controller_parent); + const struct dmi_system_id *dmi_id; + long quirks = 0; + u64 uid = 0; + + dmi_id = dmi_first_match(acpi_quirk_skip_dmi_ids); + if (!dmi_id) + return 0; + + quirks = (unsigned long)dmi_id->driver_data; + + /* uid is left at 0 on errors and 0 is not a valid UART UID */ + acpi_dev_uid_to_integer(adev, &uid); + + /* For PCI UARTs without an UID */ + if (!uid && dev_is_pci(controller_parent)) { + struct pci_dev *pdev = to_pci_dev(controller_parent); + + /* + * Devfn values for PCI UARTs on Bay Trail SoCs, which are + * the only devices where this fallback is necessary. + */ + if (pdev->devfn == PCI_DEVFN(0x1e, 3)) + uid = 1; + else if (pdev->devfn == PCI_DEVFN(0x1e, 4)) + uid = 2; + } + + if (!uid) + return 0; + + if (!dev_is_platform(controller_parent) && !dev_is_pci(controller_parent)) { + /* PNP enumerated UARTs */ + if ((quirks & ACPI_QUIRK_PNP_UART1_SKIP) && uid == 1) + *skip = true; + + return 0; + } + + if ((quirks & ACPI_QUIRK_UART1_SKIP) && uid == 1) + *skip = true; + + if (quirks & ACPI_QUIRK_UART1_TTY_UART2_SKIP) { + if (uid == 1) + return -ENODEV; /* Create tty cdev instead of serdev */ + + if (uid == 2) + *skip = true; + } + + return 0; +} + +bool acpi_quirk_skip_gpio_event_handlers(void) +{ + const struct dmi_system_id *dmi_id; + long quirks; + + dmi_id = dmi_first_match(acpi_quirk_skip_dmi_ids); + if (!dmi_id) + return false; + + quirks = (unsigned long)dmi_id->driver_data; + return (quirks & ACPI_QUIRK_SKIP_GPIO_EVENT_HANDLERS); +} +EXPORT_SYMBOL_GPL(acpi_quirk_skip_gpio_event_handlers); +#else +static int acpi_dmi_skip_serdev_enumeration(struct device *controller_parent, bool *skip) +{ + return 0; +} +#endif + +int acpi_quirk_skip_serdev_enumeration(struct device *controller_parent, bool *skip) +{ + struct acpi_device *adev = ACPI_COMPANION(controller_parent); + + *skip = false; + + /* + * The DELL0501 ACPI HID represents an UART (CID is set to PNP0501) with + * a backlight-controller attached. There is no separate ACPI device with + * an UartSerialBusV2() resource to model the backlight-controller. + * Set skip to true so that the tty core creates a serdev ctrl device. + * The backlight driver will manually create the serdev client device. + */ + if (adev && acpi_dev_hid_match(adev, "DELL0501")) { + *skip = true; + /* + * Create a platform dev for dell-uart-backlight to bind to. + * This is a static device, so no need to store the result. + */ + platform_device_register_simple("dell-uart-backlight", PLATFORM_DEVID_NONE, + NULL, 0); + return 0; + } + + return acpi_dmi_skip_serdev_enumeration(controller_parent, skip); +} +EXPORT_SYMBOL_GPL(acpi_quirk_skip_serdev_enumeration); + +/* Lists of PMIC ACPI HIDs with an (often better) native charger driver */ +static const struct { + const char *hid; + int hrv; +} acpi_skip_ac_and_battery_pmic_ids[] = { + { "INT33F4", -1 }, /* X-Powers AXP288 PMIC */ + { "INT34D3", 3 }, /* Intel Cherrytrail Whiskey Cove PMIC */ +}; + +bool acpi_quirk_skip_acpi_ac_and_battery(void) +{ + const struct dmi_system_id *dmi_id; + long quirks = 0; + int i; + + dmi_id = dmi_first_match(acpi_quirk_skip_dmi_ids); + if (dmi_id) + quirks = (unsigned long)dmi_id->driver_data; + + if (quirks & ACPI_QUIRK_SKIP_ACPI_AC_AND_BATTERY) + return true; + + if (quirks & ACPI_QUIRK_USE_ACPI_AC_AND_BATTERY) + return false; + + for (i = 0; i < ARRAY_SIZE(acpi_skip_ac_and_battery_pmic_ids); i++) { + if (acpi_dev_present(acpi_skip_ac_and_battery_pmic_ids[i].hid, "1", + acpi_skip_ac_and_battery_pmic_ids[i].hrv)) { + pr_info_once("found native %s PMIC, skipping ACPI AC and battery devices\n", + acpi_skip_ac_and_battery_pmic_ids[i].hid); + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(acpi_quirk_skip_acpi_ac_and_battery); + +/* This section provides a workaround for a specific x86 system + * which requires disabling of mwait to work correctly. + */ +static int __init acpi_proc_quirk_set_no_mwait(const struct dmi_system_id *id) +{ + pr_notice("%s detected - disabling mwait for CPU C-states\n", + id->ident); + boot_option_idle_override = IDLE_NOMWAIT; + return 0; +} + +static const struct dmi_system_id acpi_proc_quirk_mwait_dmi_table[] __initconst = { + { + .callback = acpi_proc_quirk_set_no_mwait, + .ident = "Extensa 5220", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"), + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_VERSION, "0100"), + DMI_MATCH(DMI_BOARD_NAME, "Columbia"), + }, + .driver_data = NULL, + }, + {} +}; + +void __init acpi_proc_quirk_mwait_check(void) +{ + /* + * Check whether the system is DMI table. If yes, OSPM + * should not use mwait for CPU-states. + */ + dmi_check_system(acpi_proc_quirk_mwait_dmi_table); +} |
