2019-06-04 16:11:33 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2013-03-07 06:46:20 +08:00
|
|
|
/*
|
|
|
|
* ACPI support for Intel Lynxpoint LPSS.
|
|
|
|
*
|
2015-02-03 21:29:43 +08:00
|
|
|
* Copyright (C) 2013, Intel Corporation
|
2013-03-07 06:46:20 +08:00
|
|
|
* 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>
|
2019-10-25 05:57:23 +08:00
|
|
|
#include <linux/dmi.h>
|
2013-03-07 06:46:20 +08:00
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/io.h>
|
2015-12-12 09:45:06 +08:00
|
|
|
#include <linux/mutex.h>
|
2018-09-23 21:58:08 +08:00
|
|
|
#include <linux/pci.h>
|
2013-03-07 06:46:20 +08:00
|
|
|
#include <linux/platform_device.h>
|
2019-02-19 03:34:15 +08:00
|
|
|
#include <linux/platform_data/x86/clk-lpss.h>
|
2017-01-24 02:07:43 +08:00
|
|
|
#include <linux/platform_data/x86/pmc_atom.h>
|
2016-01-07 23:46:13 +08:00
|
|
|
#include <linux/pm_domain.h>
|
2013-03-07 06:46:28 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2017-01-23 00:14:09 +08:00
|
|
|
#include <linux/pwm.h>
|
2022-02-24 07:56:22 +08:00
|
|
|
#include <linux/pxa2xx_ssp.h>
|
2018-06-13 19:17:26 +08:00
|
|
|
#include <linux/suspend.h>
|
2014-05-23 21:15:09 +08:00
|
|
|
#include <linux/delay.h>
|
2013-03-07 06:46:20 +08:00
|
|
|
|
|
|
|
#include "internal.h"
|
|
|
|
|
2014-05-30 20:34:05 +08:00
|
|
|
#ifdef CONFIG_X86_INTEL_LPSS
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
#include <asm/cpu_device_id.h>
|
2016-06-03 08:19:46 +08:00
|
|
|
#include <asm/intel-family.h>
|
2015-12-12 09:45:06 +08:00
|
|
|
#include <asm/iosf_mbi.h>
|
|
|
|
|
2014-05-30 20:34:05 +08:00
|
|
|
#define LPSS_ADDR(desc) ((unsigned long)&desc)
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
#define LPSS_CLK_SIZE 0x04
|
2013-03-07 06:46:28 +08:00
|
|
|
#define LPSS_LTR_SIZE 0x18
|
|
|
|
|
|
|
|
/* Offsets relative to LPSS_PRIVATE_OFFSET */
|
2014-05-19 19:42:07 +08:00
|
|
|
#define LPSS_CLK_DIVIDER_DEF_MASK (BIT(1) | BIT(16))
|
2014-06-17 19:33:39 +08:00
|
|
|
#define LPSS_RESETS 0x04
|
|
|
|
#define LPSS_RESETS_RESET_FUNC BIT(0)
|
|
|
|
#define LPSS_RESETS_RESET_APB BIT(1)
|
2013-03-07 06:46:28 +08:00
|
|
|
#define LPSS_GENERAL 0x08
|
|
|
|
#define LPSS_GENERAL_LTR_MODE_SW BIT(2)
|
2013-10-09 14:49:20 +08:00
|
|
|
#define LPSS_GENERAL_UART_RTS_OVRD BIT(3)
|
2013-03-07 06:46:28 +08:00
|
|
|
#define LPSS_SW_LTR 0x10
|
|
|
|
#define LPSS_AUTO_LTR 0x14
|
2014-02-11 07:35:53 +08:00
|
|
|
#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
|
2013-06-17 18:25:46 +08:00
|
|
|
#define LPSS_TX_INT 0x20
|
|
|
|
#define LPSS_TX_INT_MASK BIT(1)
|
2013-03-07 06:46:20 +08:00
|
|
|
|
2014-05-23 21:15:09 +08:00
|
|
|
#define LPSS_PRV_REG_COUNT 9
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
/* 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)
|
ACPI / LPSS: Save Cherry Trail PWM ctx registers only once (at activation)
The DSDTs on most Cherry Trail devices have an ugly clutch where the PWM
controller gets turned off from the _PS3 method of the graphics-card dev:
Method (_PS3, 0, Serialized) // _PS3: Power State 3
{
...
PWMB = PWMC /* \_SB_.PCI0.GFX0.PWMC */
PSAT |= 0x03
Local0 = PSAT /* \_SB_.PCI0.GFX0.PSAT */
...
}
Where PSAT is the power-status register of the PWM controller.
Since the i915 driver will do a pwm_get on the pwm device as it uses it to
control the LCD panel backlight, there is a device-link marking the i915
device as a consumer of the pwm device. So that the PWM controller will
always be suspended after the i915 driver suspends (which is the right
thing to do). This causes the above GFX0 PS3 AML code to run before
acpi_lpss.c calls acpi_lpss_save_ctx().
So on these devices the PWM controller will already be off when
acpi_lpss_save_ctx() runs. This causes it to read/save all 1-s (0xffffffff)
as ctx register values.
When these bogus values get restored on resume the PWM controller actually
keeps working, since most bits are reserved, but this does set bit 3 of
the LPSS General purpose register, which for the PWM controller has the
following function: "This bit is re-used to support 32kHz slow mode.
Default is 19.2MHz as PWM source clock".
This causes the clock of the PWM controller to switch from 19.2MHz to
32KHz, which is a slow-down of a factor 600. Surprisingly enough so far
there have been few bug reports about this. This is likely because the
i915 driver was hardcoding the PWM frequency to 46 KHz, which divided
by 600 would result in a PWM frequency of approx. 78 Hz, which mostly
still works fine. There are some bug reports about the LCD backlight
flickering after suspend/resume which are likely caused by this issue.
But with the upcoming patch-series to finally switch the i915 drivers
code for external PWM controllers to use the atomic API and to honor
the PWM frequency specified in the video BIOS (VBT), this becomes a much
bigger problem. On most cases the VBT specifies either 200 Hz or 20
KHz as PWM frequency, which with the mentioned issue ends up being either
1/3 Hz, where the backlight actually visible blinks on and off every 3s,
or in 33 Hz and horrible flickering of the backlight.
There are a number of possible solutions to this problem:
1. Make acpi_lpss_save_ctx() run before GFX0._PS3
Pro: Clean solution from pov of not medling with save/restore ctx code
Con: As mentioned the current ordering is the right thing to do
Con: Requires assymmetry in at what suspend/resume phase we do the save vs
restore, requiring more suspend/resume ordering hacks in already
convoluted acpi_lpss.c suspend/resume code.
2. Do some sort of save once mode for the LPSS ctx
Pro: Reasonably clean
Con: Needs a new LPSS flag + code changes to handle the flag
3. Detect we have failed to save the ctx registers and do not restore them
Pro: Not PWM specific, might help with issues on other LPSS devices too
Con: If we can get away with not restoring the ctx why bother with it at
all?
4. Do not save the ctx for CHT PWM controllers
Pro: Clean, as simple as dropping a flag?
Con: Not so simple as dropping a flag, needs a new flag to ensure that
we still do lpss_deassert_reset() on device activation.
5. Make the pwm-lpss code fixup the LPSS-context registers
Pro: Keeps acpi_lpss.c code clean
Con: Moves knowledge of LPSS-context into the pwm-lpss.c code
1 and 5 both do not seem to be a desirable way forward.
3 and 4 seem ok, but they both assume that restoring the LPSS-context
registers is not necessary. I have done a couple of test and those do
show that restoring the LPSS-context indeed does not seem to be necessary
on devices using s2idle suspend (and successfully reaching S0i3). But I
have no hardware to test deep / S3 suspend. So I'm not sure that not
restoring the context is safe.
That leaves solution 2, which is about as simple / clean as 3 and 4,
so this commit fixes the described problem by implementing a new
LPSS_SAVE_CTX_ONCE flag and setting that for the CHT PWM controllers.
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200903112337.4113-3-hdegoede@redhat.com
2020-09-03 19:23:22 +08:00
|
|
|
/*
|
|
|
|
* 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)
|
2013-05-13 20:42:44 +08:00
|
|
|
|
2013-06-17 18:25:46 +08:00
|
|
|
struct lpss_private_data;
|
2013-03-07 06:46:20 +08:00
|
|
|
|
|
|
|
struct lpss_device_desc {
|
2014-09-02 15:55:07 +08:00
|
|
|
unsigned int flags;
|
2015-03-06 21:48:38 +08:00
|
|
|
const char *clk_con_id;
|
2013-03-07 06:46:28 +08:00
|
|
|
unsigned int prv_offset;
|
2013-06-18 21:51:35 +08:00
|
|
|
size_t prv_size_override;
|
2022-02-24 07:56:21 +08:00
|
|
|
const struct property_entry *properties;
|
2013-06-17 18:25:46 +08:00
|
|
|
void (*setup)(struct lpss_private_data *pdata);
|
2018-09-23 21:58:11 +08:00
|
|
|
bool resume_from_noirq;
|
2013-03-07 06:46:20 +08:00
|
|
|
};
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
static const struct lpss_device_desc lpss_dma_desc = {
|
2015-02-03 21:29:43 +08:00
|
|
|
.flags = LPSS_CLK,
|
2013-05-08 16:55:49 +08:00
|
|
|
};
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
struct lpss_private_data {
|
2017-07-07 00:49:27 +08:00
|
|
|
struct acpi_device *adev;
|
2013-03-07 06:46:20 +08:00
|
|
|
void __iomem *mmio_base;
|
|
|
|
resource_size_t mmio_size;
|
2014-09-02 15:55:09 +08:00
|
|
|
unsigned int fixed_clk_rate;
|
2013-03-07 06:46:20 +08:00
|
|
|
struct clk *clk;
|
|
|
|
const struct lpss_device_desc *dev_desc;
|
2014-05-23 21:15:09 +08:00
|
|
|
u32 prv_reg_ctx[LPSS_PRV_REG_COUNT];
|
2013-03-07 06:46:20 +08:00
|
|
|
};
|
|
|
|
|
2018-09-09 02:08:13 +08:00
|
|
|
/* Devices which need to be in D3 before lpss_iosf_enter_d3_state() proceeds */
|
|
|
|
static u32 pmc_atom_d3_mask = 0xfe000ffe;
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
/* LPSS run time quirks */
|
|
|
|
static unsigned int lpss_quirks;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LPSS_QUIRK_ALWAYS_POWER_ON: override power state for LPSS DMA device.
|
|
|
|
*
|
2015-12-22 04:31:09 +08:00
|
|
|
* The LPSS DMA controller has neither _PS0 nor _PS3 method. Moreover
|
2015-12-12 09:45:06 +08:00
|
|
|
* 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)
|
|
|
|
|
2014-09-11 20:19:33 +08:00
|
|
|
/* UART Component Parameter Register */
|
|
|
|
#define LPSS_UART_CPR 0xF4
|
|
|
|
#define LPSS_UART_CPR_AFCE BIT(4)
|
|
|
|
|
2013-06-17 18:25:46 +08:00
|
|
|
static void lpss_uart_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2013-10-09 14:49:20 +08:00
|
|
|
unsigned int offset;
|
2014-09-11 20:19:33 +08:00
|
|
|
u32 val;
|
2013-06-17 18:25:46 +08:00
|
|
|
|
2013-10-09 14:49:20 +08:00
|
|
|
offset = pdata->dev_desc->prv_offset + LPSS_TX_INT;
|
2014-09-11 20:19:33 +08:00
|
|
|
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);
|
|
|
|
}
|
2013-06-17 18:25:46 +08:00
|
|
|
}
|
|
|
|
|
2015-02-18 19:50:17 +08:00
|
|
|
static void lpss_deassert_reset(struct lpss_private_data *pdata)
|
2014-06-17 19:33:39 +08:00
|
|
|
{
|
|
|
|
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);
|
2015-02-18 19:50:17 +08:00
|
|
|
}
|
|
|
|
|
2017-04-21 15:35:07 +08:00
|
|
|
/*
|
|
|
|
* 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",
|
2019-12-17 04:29:04 +08:00
|
|
|
"pwm_soc_backlight", 0, PWM_POLARITY_NORMAL,
|
2017-04-21 15:35:07 +08:00
|
|
|
"pwm-lpss-platform"),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void byt_pwm_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2022-09-14 00:31:41 +08:00
|
|
|
u64 uid;
|
2017-07-07 00:49:27 +08:00
|
|
|
|
|
|
|
/* Only call pwm_add_table for the first PWM controller */
|
2022-09-14 00:31:41 +08:00
|
|
|
if (acpi_dev_uid_to_integer(pdata->adev, &uid) || uid != 1)
|
2017-07-07 00:49:27 +08:00
|
|
|
return;
|
|
|
|
|
2019-12-17 04:29:04 +08:00
|
|
|
pwm_add_table(byt_pwm_lookup, ARRAY_SIZE(byt_pwm_lookup));
|
2017-04-21 15:35:07 +08:00
|
|
|
}
|
|
|
|
|
2015-02-18 19:50:17 +08:00
|
|
|
#define LPSS_I2C_ENABLE 0x6c
|
|
|
|
|
|
|
|
static void byt_i2c_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2018-09-09 02:08:13 +08:00
|
|
|
acpi_handle handle = pdata->adev->handle;
|
|
|
|
unsigned long long shared_host = 0;
|
|
|
|
acpi_status status;
|
2022-09-14 00:31:41 +08:00
|
|
|
u64 uid;
|
2018-09-09 02:08:13 +08:00
|
|
|
|
2022-09-14 00:31:41 +08:00
|
|
|
/* Expected to always be successfull, but better safe then sorry */
|
|
|
|
if (!acpi_dev_uid_to_integer(pdata->adev, &uid) && uid) {
|
2021-06-03 17:12:04 +08:00
|
|
|
/* 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));
|
|
|
|
}
|
2018-09-09 02:08:13 +08:00
|
|
|
|
2015-02-18 19:50:17 +08:00
|
|
|
lpss_deassert_reset(pdata);
|
2014-06-17 19:33:39 +08:00
|
|
|
|
2014-09-02 15:55:09 +08:00
|
|
|
if (readl(pdata->mmio_base + pdata->dev_desc->prv_offset))
|
|
|
|
pdata->fixed_clk_rate = 133000000;
|
2015-02-18 19:50:16 +08:00
|
|
|
|
|
|
|
writel(0, pdata->mmio_base + LPSS_I2C_ENABLE);
|
2014-06-17 19:33:39 +08:00
|
|
|
}
|
2014-08-01 09:06:35 +08:00
|
|
|
|
2017-01-23 00:14:09 +08:00
|
|
|
/* BSW PWM used for backlight control by the i915 driver */
|
|
|
|
static struct pwm_lookup bsw_pwm_lookup[] = {
|
|
|
|
PWM_LOOKUP_WITH_MODULE("80862288:00", 0, "0000:00:02.0",
|
2019-12-17 04:29:04 +08:00
|
|
|
"pwm_soc_backlight", 0, PWM_POLARITY_NORMAL,
|
2017-01-23 00:14:09 +08:00
|
|
|
"pwm-lpss-platform"),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void bsw_pwm_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2022-09-14 00:31:41 +08:00
|
|
|
u64 uid;
|
2017-07-07 00:49:27 +08:00
|
|
|
|
|
|
|
/* Only call pwm_add_table for the first PWM controller */
|
2022-09-14 00:31:41 +08:00
|
|
|
if (acpi_dev_uid_to_integer(pdata->adev, &uid) || uid != 1)
|
2017-07-07 00:49:27 +08:00
|
|
|
return;
|
|
|
|
|
2017-01-23 00:14:09 +08:00
|
|
|
pwm_add_table(bsw_pwm_lookup, ARRAY_SIZE(bsw_pwm_lookup));
|
|
|
|
}
|
|
|
|
|
2022-02-24 07:56:22 +08:00
|
|
|
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 = {
|
2019-08-22 16:32:00 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR
|
|
|
|
| LPSS_SAVE_CTX,
|
2014-05-19 19:42:07 +08:00
|
|
|
.prv_offset = 0x800,
|
2022-02-24 07:56:22 +08:00
|
|
|
.properties = lpt_spi_properties,
|
2014-05-19 19:42:07 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc lpt_i2c_dev_desc = {
|
2019-08-22 16:32:00 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_LTR | LPSS_SAVE_CTX,
|
2013-03-07 06:46:28 +08:00
|
|
|
.prv_offset = 0x800,
|
|
|
|
};
|
|
|
|
|
2016-08-23 16:33:27 +08:00
|
|
|
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"),
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc lpt_uart_dev_desc = {
|
2019-08-22 16:32:00 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR
|
|
|
|
| LPSS_SAVE_CTX,
|
2015-03-06 21:48:38 +08:00
|
|
|
.clk_con_id = "baudclk",
|
2013-06-17 18:25:46 +08:00
|
|
|
.prv_offset = 0x800,
|
|
|
|
.setup = lpss_uart_setup,
|
2016-08-23 16:33:27 +08:00
|
|
|
.properties = uart_properties,
|
2013-03-07 06:46:28 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc lpt_sdio_dev_desc = {
|
2014-09-02 15:55:07 +08:00
|
|
|
.flags = LPSS_LTR,
|
2013-03-07 06:46:28 +08:00
|
|
|
.prv_offset = 0x1000,
|
2013-06-18 21:51:35 +08:00
|
|
|
.prv_size_override = 0x1018,
|
2014-02-19 02:24:29 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc byt_pwm_dev_desc = {
|
2014-09-02 15:55:10 +08:00
|
|
|
.flags = LPSS_SAVE_CTX,
|
2018-04-26 20:10:24 +08:00
|
|
|
.prv_offset = 0x800,
|
2017-04-21 15:35:07 +08:00
|
|
|
.setup = byt_pwm_setup,
|
2014-02-19 02:24:29 +08:00
|
|
|
};
|
|
|
|
|
2015-08-28 00:00:55 +08:00
|
|
|
static const struct lpss_device_desc bsw_pwm_dev_desc = {
|
ACPI / LPSS: Save Cherry Trail PWM ctx registers only once (at activation)
The DSDTs on most Cherry Trail devices have an ugly clutch where the PWM
controller gets turned off from the _PS3 method of the graphics-card dev:
Method (_PS3, 0, Serialized) // _PS3: Power State 3
{
...
PWMB = PWMC /* \_SB_.PCI0.GFX0.PWMC */
PSAT |= 0x03
Local0 = PSAT /* \_SB_.PCI0.GFX0.PSAT */
...
}
Where PSAT is the power-status register of the PWM controller.
Since the i915 driver will do a pwm_get on the pwm device as it uses it to
control the LCD panel backlight, there is a device-link marking the i915
device as a consumer of the pwm device. So that the PWM controller will
always be suspended after the i915 driver suspends (which is the right
thing to do). This causes the above GFX0 PS3 AML code to run before
acpi_lpss.c calls acpi_lpss_save_ctx().
So on these devices the PWM controller will already be off when
acpi_lpss_save_ctx() runs. This causes it to read/save all 1-s (0xffffffff)
as ctx register values.
When these bogus values get restored on resume the PWM controller actually
keeps working, since most bits are reserved, but this does set bit 3 of
the LPSS General purpose register, which for the PWM controller has the
following function: "This bit is re-used to support 32kHz slow mode.
Default is 19.2MHz as PWM source clock".
This causes the clock of the PWM controller to switch from 19.2MHz to
32KHz, which is a slow-down of a factor 600. Surprisingly enough so far
there have been few bug reports about this. This is likely because the
i915 driver was hardcoding the PWM frequency to 46 KHz, which divided
by 600 would result in a PWM frequency of approx. 78 Hz, which mostly
still works fine. There are some bug reports about the LCD backlight
flickering after suspend/resume which are likely caused by this issue.
But with the upcoming patch-series to finally switch the i915 drivers
code for external PWM controllers to use the atomic API and to honor
the PWM frequency specified in the video BIOS (VBT), this becomes a much
bigger problem. On most cases the VBT specifies either 200 Hz or 20
KHz as PWM frequency, which with the mentioned issue ends up being either
1/3 Hz, where the backlight actually visible blinks on and off every 3s,
or in 33 Hz and horrible flickering of the backlight.
There are a number of possible solutions to this problem:
1. Make acpi_lpss_save_ctx() run before GFX0._PS3
Pro: Clean solution from pov of not medling with save/restore ctx code
Con: As mentioned the current ordering is the right thing to do
Con: Requires assymmetry in at what suspend/resume phase we do the save vs
restore, requiring more suspend/resume ordering hacks in already
convoluted acpi_lpss.c suspend/resume code.
2. Do some sort of save once mode for the LPSS ctx
Pro: Reasonably clean
Con: Needs a new LPSS flag + code changes to handle the flag
3. Detect we have failed to save the ctx registers and do not restore them
Pro: Not PWM specific, might help with issues on other LPSS devices too
Con: If we can get away with not restoring the ctx why bother with it at
all?
4. Do not save the ctx for CHT PWM controllers
Pro: Clean, as simple as dropping a flag?
Con: Not so simple as dropping a flag, needs a new flag to ensure that
we still do lpss_deassert_reset() on device activation.
5. Make the pwm-lpss code fixup the LPSS-context registers
Pro: Keeps acpi_lpss.c code clean
Con: Moves knowledge of LPSS-context into the pwm-lpss.c code
1 and 5 both do not seem to be a desirable way forward.
3 and 4 seem ok, but they both assume that restoring the LPSS-context
registers is not necessary. I have done a couple of test and those do
show that restoring the LPSS-context indeed does not seem to be necessary
on devices using s2idle suspend (and successfully reaching S0i3). But I
have no hardware to test deep / S3 suspend. So I'm not sure that not
restoring the context is safe.
That leaves solution 2, which is about as simple / clean as 3 and 4,
so this commit fixes the described problem by implementing a new
LPSS_SAVE_CTX_ONCE flag and setting that for the CHT PWM controllers.
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200903112337.4113-3-hdegoede@redhat.com
2020-09-03 19:23:22 +08:00
|
|
|
.flags = LPSS_SAVE_CTX_ONCE | LPSS_NO_D3_DELAY,
|
2018-04-26 20:10:24 +08:00
|
|
|
.prv_offset = 0x800,
|
2017-01-23 00:14:09 +08:00
|
|
|
.setup = bsw_pwm_setup,
|
2020-09-03 19:23:21 +08:00
|
|
|
.resume_from_noirq = true,
|
2015-08-28 00:00:55 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc byt_uart_dev_desc = {
|
2015-02-03 21:29:43 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX,
|
2015-03-06 21:48:38 +08:00
|
|
|
.clk_con_id = "baudclk",
|
2013-05-13 20:42:44 +08:00
|
|
|
.prv_offset = 0x800,
|
2013-06-17 18:25:46 +08:00
|
|
|
.setup = lpss_uart_setup,
|
2016-08-23 16:33:27 +08:00
|
|
|
.properties = uart_properties,
|
2013-05-13 20:42:44 +08:00
|
|
|
};
|
|
|
|
|
2015-08-28 00:00:55 +08:00
|
|
|
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,
|
2016-08-23 16:33:27 +08:00
|
|
|
.properties = uart_properties,
|
2015-08-28 00:00:55 +08:00
|
|
|
};
|
|
|
|
|
2022-02-24 07:56:22 +08:00
|
|
|
static const struct property_entry byt_spi_properties[] = {
|
|
|
|
PROPERTY_ENTRY_U32("intel,spi-pxa2xx-type", LPSS_BYT_SSP),
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc byt_spi_dev_desc = {
|
2015-02-03 21:29:43 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX,
|
2013-05-13 20:42:44 +08:00
|
|
|
.prv_offset = 0x400,
|
2022-02-24 07:56:22 +08:00
|
|
|
.properties = byt_spi_properties,
|
2013-05-13 20:42:44 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc byt_sdio_dev_desc = {
|
2015-02-03 21:29:43 +08:00
|
|
|
.flags = LPSS_CLK,
|
2013-05-13 20:42:44 +08:00
|
|
|
};
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
static const struct lpss_device_desc byt_i2c_dev_desc = {
|
2015-02-03 21:29:43 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_SAVE_CTX,
|
2013-05-13 20:42:44 +08:00
|
|
|
.prv_offset = 0x800,
|
2014-09-02 15:55:09 +08:00
|
|
|
.setup = byt_i2c_setup,
|
2018-09-23 21:58:11 +08:00
|
|
|
.resume_from_noirq = true,
|
2014-08-19 20:55:22 +08:00
|
|
|
};
|
|
|
|
|
2015-08-28 00:00:55 +08:00
|
|
|
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,
|
2018-09-23 21:58:11 +08:00
|
|
|
.resume_from_noirq = true,
|
2015-08-28 00:00:55 +08:00
|
|
|
};
|
|
|
|
|
2022-02-24 07:56:22 +08:00
|
|
|
static const struct property_entry bsw_spi_properties[] = {
|
|
|
|
PROPERTY_ENTRY_U32("intel,spi-pxa2xx-type", LPSS_BSW_SSP),
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
static const struct lpss_device_desc bsw_spi_dev_desc = {
|
2015-08-28 00:00:55 +08:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX
|
|
|
|
| LPSS_NO_D3_DELAY,
|
2015-02-18 19:50:17 +08:00
|
|
|
.prv_offset = 0x400,
|
|
|
|
.setup = lpss_deassert_reset,
|
2022-02-24 07:56:22 +08:00
|
|
|
.properties = bsw_spi_properties,
|
2015-02-18 19:50:17 +08:00
|
|
|
};
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
static const struct x86_cpu_id lpss_cpu_ids[] = {
|
2020-03-20 21:13:53 +08:00
|
|
|
X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL),
|
|
|
|
X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL),
|
2015-12-12 09:45:06 +08:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
2014-05-30 20:34:05 +08:00
|
|
|
#else
|
|
|
|
|
|
|
|
#define LPSS_ADDR(desc) (0UL)
|
|
|
|
|
|
|
|
#endif /* CONFIG_X86_INTEL_LPSS */
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
static const struct acpi_device_id acpi_lpss_device_ids[] = {
|
2013-05-08 16:55:49 +08:00
|
|
|
/* Generic LPSS devices */
|
2014-05-30 20:34:05 +08:00
|
|
|
{ "INTL9C60", LPSS_ADDR(lpss_dma_desc) },
|
2013-05-08 16:55:49 +08:00
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
/* Lynxpoint LPSS devices */
|
2022-02-24 07:56:22 +08:00
|
|
|
{ "INT33C0", LPSS_ADDR(lpt_spi_dev_desc) },
|
|
|
|
{ "INT33C1", LPSS_ADDR(lpt_spi_dev_desc) },
|
2014-05-30 20:34:05 +08:00
|
|
|
{ "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) },
|
2013-03-07 06:46:20 +08:00
|
|
|
{ "INT33C7", },
|
|
|
|
|
2013-05-13 20:42:44 +08:00
|
|
|
/* BayTrail LPSS devices */
|
2014-05-30 20:34:05 +08:00
|
|
|
{ "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) },
|
2013-05-13 20:42:44 +08:00
|
|
|
{ "INT33B2", },
|
2014-05-15 23:28:46 +08:00
|
|
|
{ "INT33FC", },
|
2013-05-13 20:42:44 +08:00
|
|
|
|
2014-08-19 20:55:22 +08:00
|
|
|
/* Braswell LPSS devices */
|
2018-08-27 15:45:44 +08:00
|
|
|
{ "80862286", LPSS_ADDR(lpss_dma_desc) },
|
2015-08-28 00:00:55 +08:00
|
|
|
{ "80862288", LPSS_ADDR(bsw_pwm_dev_desc) },
|
|
|
|
{ "8086228A", LPSS_ADDR(bsw_uart_dev_desc) },
|
2015-02-18 19:50:17 +08:00
|
|
|
{ "8086228E", LPSS_ADDR(bsw_spi_dev_desc) },
|
2018-08-27 15:45:44 +08:00
|
|
|
{ "808622C0", LPSS_ADDR(lpss_dma_desc) },
|
2015-08-28 00:00:55 +08:00
|
|
|
{ "808622C1", LPSS_ADDR(bsw_i2c_dev_desc) },
|
2014-08-19 20:55:22 +08:00
|
|
|
|
2015-08-28 00:00:55 +08:00
|
|
|
/* Broadwell LPSS devices */
|
2022-02-24 07:56:22 +08:00
|
|
|
{ "INT3430", LPSS_ADDR(lpt_spi_dev_desc) },
|
|
|
|
{ "INT3431", LPSS_ADDR(lpt_spi_dev_desc) },
|
2014-05-30 20:34:05 +08:00
|
|
|
{ "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) },
|
2013-11-12 17:48:19 +08:00
|
|
|
{ "INT3437", },
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
/* Wildcat Point LPSS devices */
|
2022-02-24 07:56:22 +08:00
|
|
|
{ "INT3438", LPSS_ADDR(lpt_spi_dev_desc) },
|
2014-08-01 09:06:35 +08:00
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2014-05-30 20:34:05 +08:00
|
|
|
#ifdef CONFIG_X86_INTEL_LPSS
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
/* LPSS main clock device. */
|
|
|
|
static struct platform_device *lpss_clk_dev;
|
|
|
|
|
|
|
|
static inline void lpt_register_clock_device(void)
|
|
|
|
{
|
2021-07-23 03:34:50 +08:00
|
|
|
lpss_clk_dev = platform_device_register_simple("clk-lpss-atom",
|
|
|
|
PLATFORM_DEVID_NONE,
|
|
|
|
NULL, 0);
|
2013-03-07 06:46:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int register_device_clock(struct acpi_device *adev,
|
|
|
|
struct lpss_private_data *pdata)
|
|
|
|
{
|
|
|
|
const struct lpss_device_desc *dev_desc = pdata->dev_desc;
|
2014-05-19 19:42:07 +08:00
|
|
|
const char *devname = dev_name(&adev->dev);
|
2017-10-15 19:50:34 +08:00
|
|
|
struct clk *clk;
|
2013-05-08 16:55:49 +08:00
|
|
|
struct lpss_clk_data *clk_data;
|
2014-05-19 19:42:07 +08:00
|
|
|
const char *parent, *clk_name;
|
|
|
|
void __iomem *prv_base;
|
2013-03-07 06:46:20 +08:00
|
|
|
|
|
|
|
if (!lpss_clk_dev)
|
|
|
|
lpt_register_clock_device();
|
|
|
|
|
2022-06-23 21:21:27 +08:00
|
|
|
if (IS_ERR(lpss_clk_dev))
|
|
|
|
return PTR_ERR(lpss_clk_dev);
|
|
|
|
|
2013-05-08 16:55:49 +08:00
|
|
|
clk_data = platform_get_drvdata(lpss_clk_dev);
|
|
|
|
if (!clk_data)
|
|
|
|
return -ENODEV;
|
2014-09-02 15:55:08 +08:00
|
|
|
clk = clk_data->clk;
|
2013-05-08 16:55:49 +08:00
|
|
|
|
|
|
|
if (!pdata->mmio_base
|
2013-03-07 06:46:28 +08:00
|
|
|
|| pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE)
|
2013-03-07 06:46:20 +08:00
|
|
|
return -ENODATA;
|
|
|
|
|
2013-05-13 20:42:44 +08:00
|
|
|
parent = clk_data->name;
|
2014-05-19 19:42:07 +08:00
|
|
|
prv_base = pdata->mmio_base + dev_desc->prv_offset;
|
2013-05-13 20:42:44 +08:00
|
|
|
|
2014-09-02 15:55:09 +08:00
|
|
|
if (pdata->fixed_clk_rate) {
|
|
|
|
clk = clk_register_fixed_rate(NULL, devname, parent, 0,
|
|
|
|
pdata->fixed_clk_rate);
|
|
|
|
goto out;
|
2013-05-13 20:42:44 +08:00
|
|
|
}
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
if (dev_desc->flags & LPSS_CLK_GATE) {
|
2014-05-19 19:42:07 +08:00
|
|
|
clk = clk_register_gate(NULL, devname, parent, 0,
|
|
|
|
prv_base, 0, 0, NULL);
|
|
|
|
parent = devname;
|
|
|
|
}
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
if (dev_desc->flags & LPSS_CLK_DIVIDER) {
|
2014-05-19 19:42:07 +08:00
|
|
|
/* 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,
|
2021-08-13 01:00:24 +08:00
|
|
|
CLK_FRAC_DIVIDER_POWER_OF_TWO_PS,
|
|
|
|
prv_base, 1, 15, 16, 15, 0, NULL);
|
2014-05-19 19:42:07 +08:00
|
|
|
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);
|
2013-05-13 20:42:44 +08:00
|
|
|
}
|
2014-09-02 15:55:09 +08:00
|
|
|
out:
|
2013-05-13 20:42:44 +08:00
|
|
|
if (IS_ERR(clk))
|
|
|
|
return PTR_ERR(clk);
|
2013-03-07 06:46:20 +08:00
|
|
|
|
2014-05-19 19:42:07 +08:00
|
|
|
pdata->clk = clk;
|
2015-03-06 21:48:38 +08:00
|
|
|
clk_register_clkdev(clk, dev_desc->clk_con_id, devname);
|
2013-03-07 06:46:20 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-15 20:48:47 +08:00
|
|
|
struct lpss_device_links {
|
|
|
|
const char *supplier_hid;
|
|
|
|
const char *supplier_uid;
|
|
|
|
const char *consumer_hid;
|
|
|
|
const char *consumer_uid;
|
|
|
|
u32 flags;
|
2019-10-25 05:57:23 +08:00
|
|
|
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"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{}
|
2017-12-15 20:48:47 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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[] = {
|
2019-10-25 05:57:21 +08:00
|
|
|
/* CHT External sdcard slot controller depends on PMIC I2C ctrl */
|
2017-12-15 20:48:47 +08:00
|
|
|
{"808622C1", "7", "80860F14", "3", DL_FLAG_PM_RUNTIME},
|
2019-10-25 05:57:21 +08:00
|
|
|
/* CHT iGPU depends on PMIC I2C controller */
|
2018-09-23 21:58:09 +08:00
|
|
|
{"808622C1", "7", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME},
|
2019-10-25 05:57:22 +08:00
|
|
|
/* BYT iGPU depends on the Embedded Controller I2C controller (UID 1) */
|
2019-10-25 05:57:23 +08:00
|
|
|
{"80860F41", "1", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME,
|
|
|
|
i2c1_dep_missing_dmi_ids},
|
2019-10-25 05:57:21 +08:00
|
|
|
/* BYT CR iGPU depends on PMIC I2C controller (UID 5 on CR) */
|
ACPI / LPSS: Add a device link from the GPU to the BYT I2C5 controller
On some Bay Trail systems the GPU ACPI fwnode has power-resources which
point to the PMIC, which is connected over the LPSS I2C5 controller.
This one was quite nasty to debug, unlike on CHT where the same problem
leads to errors like these:
i2c_designware 808622C1:06: controller timed out
ACPI Error: AE_ERROR, Returned by Handler for [UserDefinedRegion]
ACPI Error: Method parse/execution failed \_SB.P18W._ON, AE_ERROR
video LNXVIDEO:00: Failed to change power state to D0
On BYT the read-modify-write done by drivers/acpi/pmic/intel_pmic_xpower.c
on the AXP288 PMIC register to change the power-resource state *seems* to
succeed.
But in reality, because the I2C controller has not been resumed yet, the
read silently fails and returns the wrong value, where as the write does
succeed, writing back the wrong value for all the other power-resources
in the same register, turning off a bunch of them. Which of course does
not end well.
This commit adds a RPM consumer link from the GPU (which has a LNXVIDEO
HID) to the BYT LPSS I2C5 controller, so that the I2C controller gets
resumed before the GPU is resumed and thus before we try to change the
power-resource.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2018-09-23 21:58:10 +08:00
|
|
|
{"80860F41", "5", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME},
|
2019-10-25 05:57:21 +08:00
|
|
|
/* BYT iGPU depends on PMIC I2C controller (UID 7 on non CR) */
|
|
|
|
{"80860F41", "7", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME},
|
2017-12-15 20:48:47 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool acpi_lpss_is_supplier(struct acpi_device *adev,
|
|
|
|
const struct lpss_device_links *link)
|
|
|
|
{
|
2019-10-01 22:27:23 +08:00
|
|
|
return acpi_dev_hid_uid_match(adev, link->supplier_hid, link->supplier_uid);
|
2017-12-15 20:48:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool acpi_lpss_is_consumer(struct acpi_device *adev,
|
|
|
|
const struct lpss_device_links *link)
|
|
|
|
{
|
2019-10-01 22:27:23 +08:00
|
|
|
return acpi_dev_hid_uid_match(adev, link->consumer_hid, link->consumer_uid);
|
2017-12-15 20:48:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct hid_uid {
|
|
|
|
const char *hid;
|
|
|
|
const char *uid;
|
|
|
|
};
|
|
|
|
|
2019-06-15 01:53:59 +08:00
|
|
|
static int match_hid_uid(struct device *dev, const void *data)
|
2017-12-15 20:48:47 +08:00
|
|
|
{
|
|
|
|
struct acpi_device *adev = ACPI_COMPANION(dev);
|
2019-06-15 01:53:59 +08:00
|
|
|
const struct hid_uid *id = data;
|
2017-12-15 20:48:47 +08:00
|
|
|
|
|
|
|
if (!adev)
|
|
|
|
return 0;
|
|
|
|
|
2019-10-01 22:27:23 +08:00
|
|
|
return acpi_dev_hid_uid_match(adev, id->hid, id->uid);
|
2017-12-15 20:48:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device *acpi_lpss_find_device(const char *hid, const char *uid)
|
|
|
|
{
|
2018-09-23 21:58:08 +08:00
|
|
|
struct device *dev;
|
|
|
|
|
2017-12-15 20:48:47 +08:00
|
|
|
struct hid_uid data = {
|
|
|
|
.hid = hid,
|
|
|
|
.uid = uid,
|
|
|
|
};
|
|
|
|
|
2018-09-23 21:58:08 +08:00
|
|
|
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);
|
2017-12-15 20:48:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle)
|
|
|
|
{
|
|
|
|
struct acpi_handle_list dep_devices;
|
|
|
|
acpi_status status;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!acpi_has_method(adev->handle, "_DEP"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
status = acpi_evaluate_reference(adev->handle, "_DEP", NULL,
|
|
|
|
&dep_devices);
|
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
|
dev_dbg(&adev->dev, "Failed to evaluate _DEP.\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < dep_devices.count; i++) {
|
|
|
|
if (dep_devices.handles[i] == handle)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-10-25 05:57:23 +08:00
|
|
|
if ((link->dep_missing_ids && dmi_check_system(link->dep_missing_ids))
|
|
|
|
|| acpi_lpss_dep(ACPI_COMPANION(dev2), ACPI_HANDLE(dev1)))
|
2017-12-15 20:48:47 +08:00
|
|
|
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;
|
|
|
|
|
2019-10-25 05:57:23 +08:00
|
|
|
if ((link->dep_missing_ids && dmi_check_system(link->dep_missing_ids))
|
|
|
|
|| acpi_lpss_dep(ACPI_COMPANION(dev1), ACPI_HANDLE(dev2)))
|
2017-12-15 20:48:47 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
static int acpi_lpss_create_device(struct acpi_device *adev,
|
|
|
|
const struct acpi_device_id *id)
|
|
|
|
{
|
2015-06-13 20:26:50 +08:00
|
|
|
const struct lpss_device_desc *dev_desc;
|
2013-03-07 06:46:20 +08:00
|
|
|
struct lpss_private_data *pdata;
|
2015-02-05 13:44:43 +08:00
|
|
|
struct resource_entry *rentry;
|
2013-03-07 06:46:20 +08:00
|
|
|
struct list_head resource_list;
|
2014-05-25 20:38:52 +08:00
|
|
|
struct platform_device *pdev;
|
2013-03-07 06:46:20 +08:00
|
|
|
int ret;
|
|
|
|
|
2015-06-13 20:26:50 +08:00
|
|
|
dev_desc = (const struct lpss_device_desc *)id->driver_data;
|
2014-05-25 20:38:52 +08:00
|
|
|
if (!dev_desc) {
|
2016-11-03 22:21:26 +08:00
|
|
|
pdev = acpi_create_platform_device(adev, NULL);
|
2014-05-25 20:38:52 +08:00
|
|
|
return IS_ERR_OR_NULL(pdev) ? PTR_ERR(pdev) : 1;
|
|
|
|
}
|
2013-03-07 06:46:20 +08:00
|
|
|
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
|
|
|
if (!pdata)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&resource_list);
|
2022-08-16 18:16:28 +08:00
|
|
|
ret = acpi_dev_get_memory_resources(adev, &resource_list);
|
2013-03-07 06:46:20 +08:00
|
|
|
if (ret < 0)
|
|
|
|
goto err_out;
|
|
|
|
|
2022-08-29 22:11:28 +08:00
|
|
|
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);
|
|
|
|
}
|
2013-03-07 06:46:20 +08:00
|
|
|
|
|
|
|
acpi_dev_free_resource_list(&resource_list);
|
|
|
|
|
2015-07-07 06:31:47 +08:00
|
|
|
if (!pdata->mmio_base) {
|
2018-01-15 04:01:48 +08:00
|
|
|
/* Avoid acpi_bus_attach() instantiating a pdev for this dev. */
|
|
|
|
adev->pnp.type.platform_id = 0;
|
2022-08-29 22:11:29 +08:00
|
|
|
goto out_free;
|
2015-07-07 06:31:47 +08:00
|
|
|
}
|
|
|
|
|
2017-07-07 00:49:27 +08:00
|
|
|
pdata->adev = adev;
|
2013-09-02 18:30:25 +08:00
|
|
|
pdata->dev_desc = dev_desc;
|
|
|
|
|
2014-09-02 15:55:09 +08:00
|
|
|
if (dev_desc->setup)
|
|
|
|
dev_desc->setup(pdata);
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
if (dev_desc->flags & LPSS_CLK) {
|
2013-03-07 06:46:20 +08:00
|
|
|
ret = register_device_clock(adev, pdata);
|
2022-08-29 22:11:29 +08:00
|
|
|
if (ret)
|
|
|
|
goto out_free;
|
2013-03-07 06:46:20 +08:00
|
|
|
}
|
|
|
|
|
2013-06-19 06:45:34 +08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-12-08 20:59:24 +08:00
|
|
|
acpi_device_fix_up_power(adev);
|
2013-06-19 06:45:34 +08:00
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
adev->driver_data = pdata;
|
2016-11-03 22:21:26 +08:00
|
|
|
pdev = acpi_create_platform_device(adev, dev_desc->properties);
|
2022-08-29 22:11:29 +08:00
|
|
|
if (IS_ERR_OR_NULL(pdev)) {
|
|
|
|
adev->driver_data = NULL;
|
|
|
|
ret = PTR_ERR(pdev);
|
|
|
|
goto err_out;
|
2014-05-25 20:38:52 +08:00
|
|
|
}
|
2013-03-07 06:46:20 +08:00
|
|
|
|
2022-08-29 22:11:29 +08:00
|
|
|
acpi_lpss_create_device_links(adev, pdev);
|
|
|
|
return 1;
|
2013-03-07 06:46:20 +08:00
|
|
|
|
2022-08-29 22:11:29 +08:00
|
|
|
out_free:
|
|
|
|
/* Skip the device, but continue the namespace scan */
|
|
|
|
ret = 0;
|
|
|
|
err_out:
|
2013-03-07 06:46:20 +08:00
|
|
|
kfree(pdata);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-02-11 07:35:53 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-03-07 06:46:28 +08:00
|
|
|
static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val)
|
|
|
|
{
|
2021-10-11 01:23:01 +08:00
|
|
|
struct acpi_device *adev = ACPI_COMPANION(dev);
|
2013-03-07 06:46:28 +08:00
|
|
|
struct lpss_private_data *pdata;
|
|
|
|
unsigned long flags;
|
|
|
|
int ret;
|
|
|
|
|
2021-10-11 01:23:01 +08:00
|
|
|
if (WARN_ON(!adev))
|
|
|
|
return -ENODEV;
|
2013-03-07 06:46:28 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2014-02-11 07:35:53 +08:00
|
|
|
*val = __lpss_reg_read(pdata, reg);
|
2021-10-11 01:23:01 +08:00
|
|
|
ret = 0;
|
2013-03-07 06:46:28 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2021-10-13 11:24:50 +08:00
|
|
|
return sysfs_emit(buf, "%08x\n", ltr_value);
|
2013-03-07 06:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2017-06-30 20:53:50 +08:00
|
|
|
static const struct attribute_group lpss_attr_group = {
|
2013-03-07 06:46:28 +08:00
|
|
|
.attrs = lpss_attrs,
|
|
|
|
.name = "lpss_ltr",
|
|
|
|
};
|
|
|
|
|
2014-02-11 07:35:53 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-23 21:15:09 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
/**
|
|
|
|
* acpi_lpss_save_ctx() - Save the private registers of LPSS device
|
|
|
|
* @dev: LPSS device
|
2014-11-06 00:34:45 +08:00
|
|
|
* @pdata: pointer to the private data of the LPSS device
|
2014-05-23 21:15:09 +08:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2014-11-06 00:34:45 +08:00
|
|
|
static void acpi_lpss_save_ctx(struct device *dev,
|
|
|
|
struct lpss_private_data *pdata)
|
2014-05-23 21:15:09 +08:00
|
|
|
{
|
|
|
|
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
|
2014-11-06 00:34:45 +08:00
|
|
|
* @pdata: pointer to the private data of the LPSS device
|
2014-05-23 21:15:09 +08:00
|
|
|
*
|
|
|
|
* Restores the registers that were previously stored with acpi_lpss_save_ctx().
|
|
|
|
*/
|
2014-11-06 00:34:45 +08:00
|
|
|
static void acpi_lpss_restore_ctx(struct device *dev,
|
|
|
|
struct lpss_private_data *pdata)
|
2014-05-23 21:15:09 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
2015-12-05 05:49:21 +08:00
|
|
|
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)
|
|
|
|
{
|
2014-05-23 21:15:09 +08:00
|
|
|
/*
|
|
|
|
* 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
|
2015-08-28 00:00:55 +08:00
|
|
|
* transition. However some platforms like BSW does not need this delay.
|
2014-05-23 21:15:09 +08:00
|
|
|
*/
|
2015-08-28 00:00:55 +08:00
|
|
|
unsigned int delay = 10; /* default 10ms delay */
|
|
|
|
|
|
|
|
if (pdata->dev_desc->flags & LPSS_NO_D3_DELAY)
|
|
|
|
delay = 0;
|
|
|
|
|
|
|
|
msleep(delay);
|
2014-05-23 21:15:09 +08:00
|
|
|
}
|
|
|
|
|
2015-12-05 05:49:20 +08:00
|
|
|
static int acpi_lpss_activate(struct device *dev)
|
|
|
|
{
|
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
|
|
|
|
2017-10-11 00:49:22 +08:00
|
|
|
ret = acpi_dev_resume(dev);
|
2015-12-05 05:49:20 +08:00
|
|
|
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.
|
|
|
|
*/
|
ACPI / LPSS: Save Cherry Trail PWM ctx registers only once (at activation)
The DSDTs on most Cherry Trail devices have an ugly clutch where the PWM
controller gets turned off from the _PS3 method of the graphics-card dev:
Method (_PS3, 0, Serialized) // _PS3: Power State 3
{
...
PWMB = PWMC /* \_SB_.PCI0.GFX0.PWMC */
PSAT |= 0x03
Local0 = PSAT /* \_SB_.PCI0.GFX0.PSAT */
...
}
Where PSAT is the power-status register of the PWM controller.
Since the i915 driver will do a pwm_get on the pwm device as it uses it to
control the LCD panel backlight, there is a device-link marking the i915
device as a consumer of the pwm device. So that the PWM controller will
always be suspended after the i915 driver suspends (which is the right
thing to do). This causes the above GFX0 PS3 AML code to run before
acpi_lpss.c calls acpi_lpss_save_ctx().
So on these devices the PWM controller will already be off when
acpi_lpss_save_ctx() runs. This causes it to read/save all 1-s (0xffffffff)
as ctx register values.
When these bogus values get restored on resume the PWM controller actually
keeps working, since most bits are reserved, but this does set bit 3 of
the LPSS General purpose register, which for the PWM controller has the
following function: "This bit is re-used to support 32kHz slow mode.
Default is 19.2MHz as PWM source clock".
This causes the clock of the PWM controller to switch from 19.2MHz to
32KHz, which is a slow-down of a factor 600. Surprisingly enough so far
there have been few bug reports about this. This is likely because the
i915 driver was hardcoding the PWM frequency to 46 KHz, which divided
by 600 would result in a PWM frequency of approx. 78 Hz, which mostly
still works fine. There are some bug reports about the LCD backlight
flickering after suspend/resume which are likely caused by this issue.
But with the upcoming patch-series to finally switch the i915 drivers
code for external PWM controllers to use the atomic API and to honor
the PWM frequency specified in the video BIOS (VBT), this becomes a much
bigger problem. On most cases the VBT specifies either 200 Hz or 20
KHz as PWM frequency, which with the mentioned issue ends up being either
1/3 Hz, where the backlight actually visible blinks on and off every 3s,
or in 33 Hz and horrible flickering of the backlight.
There are a number of possible solutions to this problem:
1. Make acpi_lpss_save_ctx() run before GFX0._PS3
Pro: Clean solution from pov of not medling with save/restore ctx code
Con: As mentioned the current ordering is the right thing to do
Con: Requires assymmetry in at what suspend/resume phase we do the save vs
restore, requiring more suspend/resume ordering hacks in already
convoluted acpi_lpss.c suspend/resume code.
2. Do some sort of save once mode for the LPSS ctx
Pro: Reasonably clean
Con: Needs a new LPSS flag + code changes to handle the flag
3. Detect we have failed to save the ctx registers and do not restore them
Pro: Not PWM specific, might help with issues on other LPSS devices too
Con: If we can get away with not restoring the ctx why bother with it at
all?
4. Do not save the ctx for CHT PWM controllers
Pro: Clean, as simple as dropping a flag?
Con: Not so simple as dropping a flag, needs a new flag to ensure that
we still do lpss_deassert_reset() on device activation.
5. Make the pwm-lpss code fixup the LPSS-context registers
Pro: Keeps acpi_lpss.c code clean
Con: Moves knowledge of LPSS-context into the pwm-lpss.c code
1 and 5 both do not seem to be a desirable way forward.
3 and 4 seem ok, but they both assume that restoring the LPSS-context
registers is not necessary. I have done a couple of test and those do
show that restoring the LPSS-context indeed does not seem to be necessary
on devices using s2idle suspend (and successfully reaching S0i3). But I
have no hardware to test deep / S3 suspend. So I'm not sure that not
restoring the context is safe.
That leaves solution 2, which is about as simple / clean as 3 and 4,
so this commit fixes the described problem by implementing a new
LPSS_SAVE_CTX_ONCE flag and setting that for the CHT PWM controllers.
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200903112337.4113-3-hdegoede@redhat.com
2020-09-03 19:23:22 +08:00
|
|
|
if (pdata->dev_desc->flags & (LPSS_SAVE_CTX | LPSS_SAVE_CTX_ONCE))
|
2015-12-05 05:49:20 +08:00
|
|
|
lpss_deassert_reset(pdata);
|
|
|
|
|
ACPI / LPSS: Save Cherry Trail PWM ctx registers only once (at activation)
The DSDTs on most Cherry Trail devices have an ugly clutch where the PWM
controller gets turned off from the _PS3 method of the graphics-card dev:
Method (_PS3, 0, Serialized) // _PS3: Power State 3
{
...
PWMB = PWMC /* \_SB_.PCI0.GFX0.PWMC */
PSAT |= 0x03
Local0 = PSAT /* \_SB_.PCI0.GFX0.PSAT */
...
}
Where PSAT is the power-status register of the PWM controller.
Since the i915 driver will do a pwm_get on the pwm device as it uses it to
control the LCD panel backlight, there is a device-link marking the i915
device as a consumer of the pwm device. So that the PWM controller will
always be suspended after the i915 driver suspends (which is the right
thing to do). This causes the above GFX0 PS3 AML code to run before
acpi_lpss.c calls acpi_lpss_save_ctx().
So on these devices the PWM controller will already be off when
acpi_lpss_save_ctx() runs. This causes it to read/save all 1-s (0xffffffff)
as ctx register values.
When these bogus values get restored on resume the PWM controller actually
keeps working, since most bits are reserved, but this does set bit 3 of
the LPSS General purpose register, which for the PWM controller has the
following function: "This bit is re-used to support 32kHz slow mode.
Default is 19.2MHz as PWM source clock".
This causes the clock of the PWM controller to switch from 19.2MHz to
32KHz, which is a slow-down of a factor 600. Surprisingly enough so far
there have been few bug reports about this. This is likely because the
i915 driver was hardcoding the PWM frequency to 46 KHz, which divided
by 600 would result in a PWM frequency of approx. 78 Hz, which mostly
still works fine. There are some bug reports about the LCD backlight
flickering after suspend/resume which are likely caused by this issue.
But with the upcoming patch-series to finally switch the i915 drivers
code for external PWM controllers to use the atomic API and to honor
the PWM frequency specified in the video BIOS (VBT), this becomes a much
bigger problem. On most cases the VBT specifies either 200 Hz or 20
KHz as PWM frequency, which with the mentioned issue ends up being either
1/3 Hz, where the backlight actually visible blinks on and off every 3s,
or in 33 Hz and horrible flickering of the backlight.
There are a number of possible solutions to this problem:
1. Make acpi_lpss_save_ctx() run before GFX0._PS3
Pro: Clean solution from pov of not medling with save/restore ctx code
Con: As mentioned the current ordering is the right thing to do
Con: Requires assymmetry in at what suspend/resume phase we do the save vs
restore, requiring more suspend/resume ordering hacks in already
convoluted acpi_lpss.c suspend/resume code.
2. Do some sort of save once mode for the LPSS ctx
Pro: Reasonably clean
Con: Needs a new LPSS flag + code changes to handle the flag
3. Detect we have failed to save the ctx registers and do not restore them
Pro: Not PWM specific, might help with issues on other LPSS devices too
Con: If we can get away with not restoring the ctx why bother with it at
all?
4. Do not save the ctx for CHT PWM controllers
Pro: Clean, as simple as dropping a flag?
Con: Not so simple as dropping a flag, needs a new flag to ensure that
we still do lpss_deassert_reset() on device activation.
5. Make the pwm-lpss code fixup the LPSS-context registers
Pro: Keeps acpi_lpss.c code clean
Con: Moves knowledge of LPSS-context into the pwm-lpss.c code
1 and 5 both do not seem to be a desirable way forward.
3 and 4 seem ok, but they both assume that restoring the LPSS-context
registers is not necessary. I have done a couple of test and those do
show that restoring the LPSS-context indeed does not seem to be necessary
on devices using s2idle suspend (and successfully reaching S0i3). But I
have no hardware to test deep / S3 suspend. So I'm not sure that not
restoring the context is safe.
That leaves solution 2, which is about as simple / clean as 3 and 4,
so this commit fixes the described problem by implementing a new
LPSS_SAVE_CTX_ONCE flag and setting that for the CHT PWM controllers.
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200903112337.4113-3-hdegoede@redhat.com
2020-09-03 19:23:22 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
if (pdata->dev_desc->flags & LPSS_SAVE_CTX_ONCE)
|
|
|
|
acpi_lpss_save_ctx(dev, pdata);
|
|
|
|
#endif
|
|
|
|
|
2015-12-05 05:49:20 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void acpi_lpss_dismiss(struct device *dev)
|
|
|
|
{
|
2017-10-14 23:43:15 +08:00
|
|
|
acpi_dev_suspend(dev, false);
|
2015-12-05 05:49:20 +08:00
|
|
|
}
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
/* 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)
|
2016-11-17 22:30:06 +08:00
|
|
|
#define LPSS_GPIODEF0_DMA_LLP BIT(13)
|
2015-12-12 09:45:06 +08:00
|
|
|
|
|
|
|
static DEFINE_MUTEX(lpss_iosf_mutex);
|
2018-09-03 10:00:07 +08:00
|
|
|
static bool lpss_iosf_d3_entered = true;
|
2015-12-12 09:45:06 +08:00
|
|
|
|
|
|
|
static void lpss_iosf_enter_d3_state(void)
|
|
|
|
{
|
|
|
|
u32 value1 = 0;
|
2016-11-17 22:30:06 +08:00
|
|
|
u32 mask1 = LPSS_GPIODEF0_DMA_D3_MASK | LPSS_GPIODEF0_DMA_LLP;
|
2015-12-12 09:45:06 +08:00
|
|
|
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.
|
|
|
|
*/
|
2018-09-09 02:08:13 +08:00
|
|
|
u32 func_dis, d3_sts_0, pmc_status;
|
2015-12-12 09:45:06 +08:00
|
|
|
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.
|
|
|
|
*/
|
2018-09-09 02:08:13 +08:00
|
|
|
pmc_status = (~(d3_sts_0 | func_dis)) & pmc_atom_d3_mask;
|
2015-12-12 09:45:06 +08:00
|
|
|
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);
|
2018-07-26 16:58:20 +08:00
|
|
|
|
|
|
|
lpss_iosf_d3_entered = true;
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
exit:
|
|
|
|
mutex_unlock(&lpss_iosf_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lpss_iosf_exit_d3_state(void)
|
|
|
|
{
|
2016-11-17 22:30:06 +08:00
|
|
|
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;
|
2015-12-12 09:45:06 +08:00
|
|
|
u32 value2 = LPSS_PMCSR_D0;
|
|
|
|
u32 mask2 = LPSS_PMCSR_Dx_MASK;
|
|
|
|
|
|
|
|
mutex_lock(&lpss_iosf_mutex);
|
|
|
|
|
2018-07-26 16:58:20 +08:00
|
|
|
if (!lpss_iosf_d3_entered)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
lpss_iosf_d3_entered = false;
|
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
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);
|
|
|
|
|
2018-07-26 16:58:20 +08:00
|
|
|
exit:
|
2015-12-12 09:45:06 +08:00
|
|
|
mutex_unlock(&lpss_iosf_mutex);
|
|
|
|
}
|
|
|
|
|
2018-07-26 16:58:20 +08:00
|
|
|
static int acpi_lpss_suspend(struct device *dev, bool wakeup)
|
2014-05-23 21:15:09 +08:00
|
|
|
{
|
2014-11-06 00:34:45 +08:00
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
2014-05-23 21:15:09 +08:00
|
|
|
|
2014-11-06 00:34:45 +08:00
|
|
|
if (pdata->dev_desc->flags & LPSS_SAVE_CTX)
|
|
|
|
acpi_lpss_save_ctx(dev, pdata);
|
|
|
|
|
2017-10-16 09:29:55 +08:00
|
|
|
ret = acpi_dev_suspend(dev, wakeup);
|
2015-12-12 09:45:06 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-07-26 16:58:20 +08:00
|
|
|
if (acpi_target_system_state() == ACPI_STATE_S0 &&
|
2018-06-13 19:17:26 +08:00
|
|
|
lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available())
|
2015-12-12 09:45:06 +08:00
|
|
|
lpss_iosf_enter_d3_state();
|
|
|
|
|
|
|
|
return ret;
|
2014-05-23 21:15:09 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 16:58:20 +08:00
|
|
|
static int acpi_lpss_resume(struct device *dev)
|
2014-05-23 21:15:09 +08:00
|
|
|
{
|
2014-11-06 00:34:45 +08:00
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
2014-05-23 21:15:09 +08:00
|
|
|
|
2015-12-12 09:45:06 +08:00
|
|
|
/*
|
|
|
|
* This call is kept first to be in symmetry with
|
|
|
|
* acpi_lpss_runtime_suspend() one.
|
|
|
|
*/
|
2018-07-26 16:58:20 +08:00
|
|
|
if (lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available())
|
2015-12-12 09:45:06 +08:00
|
|
|
lpss_iosf_exit_d3_state();
|
|
|
|
|
2017-10-11 00:49:22 +08:00
|
|
|
ret = acpi_dev_resume(dev);
|
2014-05-23 21:15:09 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-12-05 05:49:21 +08:00
|
|
|
acpi_lpss_d3_to_d0_delay(pdata);
|
|
|
|
|
ACPI / LPSS: Save Cherry Trail PWM ctx registers only once (at activation)
The DSDTs on most Cherry Trail devices have an ugly clutch where the PWM
controller gets turned off from the _PS3 method of the graphics-card dev:
Method (_PS3, 0, Serialized) // _PS3: Power State 3
{
...
PWMB = PWMC /* \_SB_.PCI0.GFX0.PWMC */
PSAT |= 0x03
Local0 = PSAT /* \_SB_.PCI0.GFX0.PSAT */
...
}
Where PSAT is the power-status register of the PWM controller.
Since the i915 driver will do a pwm_get on the pwm device as it uses it to
control the LCD panel backlight, there is a device-link marking the i915
device as a consumer of the pwm device. So that the PWM controller will
always be suspended after the i915 driver suspends (which is the right
thing to do). This causes the above GFX0 PS3 AML code to run before
acpi_lpss.c calls acpi_lpss_save_ctx().
So on these devices the PWM controller will already be off when
acpi_lpss_save_ctx() runs. This causes it to read/save all 1-s (0xffffffff)
as ctx register values.
When these bogus values get restored on resume the PWM controller actually
keeps working, since most bits are reserved, but this does set bit 3 of
the LPSS General purpose register, which for the PWM controller has the
following function: "This bit is re-used to support 32kHz slow mode.
Default is 19.2MHz as PWM source clock".
This causes the clock of the PWM controller to switch from 19.2MHz to
32KHz, which is a slow-down of a factor 600. Surprisingly enough so far
there have been few bug reports about this. This is likely because the
i915 driver was hardcoding the PWM frequency to 46 KHz, which divided
by 600 would result in a PWM frequency of approx. 78 Hz, which mostly
still works fine. There are some bug reports about the LCD backlight
flickering after suspend/resume which are likely caused by this issue.
But with the upcoming patch-series to finally switch the i915 drivers
code for external PWM controllers to use the atomic API and to honor
the PWM frequency specified in the video BIOS (VBT), this becomes a much
bigger problem. On most cases the VBT specifies either 200 Hz or 20
KHz as PWM frequency, which with the mentioned issue ends up being either
1/3 Hz, where the backlight actually visible blinks on and off every 3s,
or in 33 Hz and horrible flickering of the backlight.
There are a number of possible solutions to this problem:
1. Make acpi_lpss_save_ctx() run before GFX0._PS3
Pro: Clean solution from pov of not medling with save/restore ctx code
Con: As mentioned the current ordering is the right thing to do
Con: Requires assymmetry in at what suspend/resume phase we do the save vs
restore, requiring more suspend/resume ordering hacks in already
convoluted acpi_lpss.c suspend/resume code.
2. Do some sort of save once mode for the LPSS ctx
Pro: Reasonably clean
Con: Needs a new LPSS flag + code changes to handle the flag
3. Detect we have failed to save the ctx registers and do not restore them
Pro: Not PWM specific, might help with issues on other LPSS devices too
Con: If we can get away with not restoring the ctx why bother with it at
all?
4. Do not save the ctx for CHT PWM controllers
Pro: Clean, as simple as dropping a flag?
Con: Not so simple as dropping a flag, needs a new flag to ensure that
we still do lpss_deassert_reset() on device activation.
5. Make the pwm-lpss code fixup the LPSS-context registers
Pro: Keeps acpi_lpss.c code clean
Con: Moves knowledge of LPSS-context into the pwm-lpss.c code
1 and 5 both do not seem to be a desirable way forward.
3 and 4 seem ok, but they both assume that restoring the LPSS-context
registers is not necessary. I have done a couple of test and those do
show that restoring the LPSS-context indeed does not seem to be necessary
on devices using s2idle suspend (and successfully reaching S0i3). But I
have no hardware to test deep / S3 suspend. So I'm not sure that not
restoring the context is safe.
That leaves solution 2, which is about as simple / clean as 3 and 4,
so this commit fixes the described problem by implementing a new
LPSS_SAVE_CTX_ONCE flag and setting that for the CHT PWM controllers.
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200903112337.4113-3-hdegoede@redhat.com
2020-09-03 19:23:22 +08:00
|
|
|
if (pdata->dev_desc->flags & (LPSS_SAVE_CTX | LPSS_SAVE_CTX_ONCE))
|
2014-11-06 00:34:45 +08:00
|
|
|
acpi_lpss_restore_ctx(dev, pdata);
|
|
|
|
|
2017-10-16 09:29:55 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2018-09-23 21:58:11 +08:00
|
|
|
static int acpi_lpss_do_suspend_late(struct device *dev)
|
2017-10-16 09:29:55 +08:00
|
|
|
{
|
ACPI / PM: Take SMART_SUSPEND driver flag into account
Make the ACPI PM domain take DPM_FLAG_SMART_SUSPEND into account in
its system suspend callbacks.
[Note that the pm_runtime_suspended() check in acpi_dev_needs_resume()
is an optimization, because if is not passed, all of the subsequent
checks may be skipped and some of them are much more overhead in
general.]
Also use the observation that if the device is in runtime suspend
at the beginning of the "late" phase of a system-wide suspend-like
transition, its state cannot change going forward (runtime PM is
disabled for it at that time) until the transition is over and the
subsequent system-wide PM callbacks should be skipped for it (as
they generally assume the device to not be suspended), so add
checks for that in acpi_subsys_suspend_late/noirq() and
acpi_subsys_freeze_late/noirq().
Moreover, if acpi_subsys_resume_noirq() is called during the
subsequent system-wide resume transition and if the device was left
in runtime suspend previously, its runtime PM status needs to be
changed to "active" as it is going to be put into the full-power
state going forward, so add a check for that too in there.
In turn, if acpi_subsys_thaw_noirq() runs after the device has been
left in runtime suspend, the subsequent "thaw" callbacks need
to be skipped for it (as they may not work correctly with a
suspended device), so set the power.direct_complete flag for the
device then to make the PM core skip those callbacks.
On top of the above, make the analogous changes in the acpi_lpss
driver that uses the ACPI PM domain callbacks.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-27 16:10:16 +08:00
|
|
|
int ret;
|
|
|
|
|
2020-04-19 00:52:48 +08:00
|
|
|
if (dev_pm_skip_suspend(dev))
|
ACPI / PM: Take SMART_SUSPEND driver flag into account
Make the ACPI PM domain take DPM_FLAG_SMART_SUSPEND into account in
its system suspend callbacks.
[Note that the pm_runtime_suspended() check in acpi_dev_needs_resume()
is an optimization, because if is not passed, all of the subsequent
checks may be skipped and some of them are much more overhead in
general.]
Also use the observation that if the device is in runtime suspend
at the beginning of the "late" phase of a system-wide suspend-like
transition, its state cannot change going forward (runtime PM is
disabled for it at that time) until the transition is over and the
subsequent system-wide PM callbacks should be skipped for it (as
they generally assume the device to not be suspended), so add
checks for that in acpi_subsys_suspend_late/noirq() and
acpi_subsys_freeze_late/noirq().
Moreover, if acpi_subsys_resume_noirq() is called during the
subsequent system-wide resume transition and if the device was left
in runtime suspend previously, its runtime PM status needs to be
changed to "active" as it is going to be put into the full-power
state going forward, so add a check for that too in there.
In turn, if acpi_subsys_thaw_noirq() runs after the device has been
left in runtime suspend, the subsequent "thaw" callbacks need
to be skipped for it (as they may not work correctly with a
suspended device), so set the power.direct_complete flag for the
device then to make the PM core skip those callbacks.
On top of the above, make the analogous changes in the acpi_lpss
driver that uses the ACPI PM domain callbacks.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-27 16:10:16 +08:00
|
|
|
return 0;
|
2017-10-16 09:29:55 +08:00
|
|
|
|
ACPI / PM: Take SMART_SUSPEND driver flag into account
Make the ACPI PM domain take DPM_FLAG_SMART_SUSPEND into account in
its system suspend callbacks.
[Note that the pm_runtime_suspended() check in acpi_dev_needs_resume()
is an optimization, because if is not passed, all of the subsequent
checks may be skipped and some of them are much more overhead in
general.]
Also use the observation that if the device is in runtime suspend
at the beginning of the "late" phase of a system-wide suspend-like
transition, its state cannot change going forward (runtime PM is
disabled for it at that time) until the transition is over and the
subsequent system-wide PM callbacks should be skipped for it (as
they generally assume the device to not be suspended), so add
checks for that in acpi_subsys_suspend_late/noirq() and
acpi_subsys_freeze_late/noirq().
Moreover, if acpi_subsys_resume_noirq() is called during the
subsequent system-wide resume transition and if the device was left
in runtime suspend previously, its runtime PM status needs to be
changed to "active" as it is going to be put into the full-power
state going forward, so add a check for that too in there.
In turn, if acpi_subsys_thaw_noirq() runs after the device has been
left in runtime suspend, the subsequent "thaw" callbacks need
to be skipped for it (as they may not work correctly with a
suspended device), so set the power.direct_complete flag for the
device then to make the PM core skip those callbacks.
On top of the above, make the analogous changes in the acpi_lpss
driver that uses the ACPI PM domain callbacks.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-27 16:10:16 +08:00
|
|
|
ret = pm_generic_suspend_late(dev);
|
2018-07-26 16:58:20 +08:00
|
|
|
return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev));
|
2017-10-16 09:29:55 +08:00
|
|
|
}
|
|
|
|
|
2018-09-23 21:58:11 +08:00
|
|
|
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) {
|
2019-07-01 18:54:29 +08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-09-23 21:58:11 +08:00
|
|
|
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)
|
2017-10-16 09:29:55 +08:00
|
|
|
{
|
2018-07-26 16:58:20 +08:00
|
|
|
int ret = acpi_lpss_resume(dev);
|
2017-10-16 09:29:55 +08:00
|
|
|
|
|
|
|
return ret ? ret : pm_generic_resume_early(dev);
|
|
|
|
}
|
2018-09-23 21:58:11 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-04-19 00:52:30 +08:00
|
|
|
if (dev_pm_skip_resume(dev))
|
PM: sleep: core: Do not skip callbacks in the resume phase
The current code in device_resume_noirq() causes the entire early
resume and resume phases of device suspend to be skipped for
devices for which the noirq resume phase have been skipped (due
to the LEAVE_SUSPENDED flag being set) on the premise that those
devices should stay in runtime-suspend after system-wide resume.
However, that may not be correct in two situations. First, the
middle layer (subsystem) noirq resume callback may be missing for
a given device, but its early resume callback may be present and it
may need to do something even if it decides to skip the driver
callback. Second, if the device's wakeup settings were adjusted
in the suspend phase without resuming the device (that was in
runtime suspend at that time), they most likely need to be
adjusted again in the resume phase and so the driver callback
in that phase needs to be run.
For the above reason, modify the core to allow the middle layer
->resume_late callback to run even if its ->resume_noirq callback
is missing (and the core has skipped the driver-level callback
in that phase) and to allow all device callbacks to run in the
resume phase. Also make the core set the PM-runtime status of
devices with SMART_SUSPEND set whose resume callbacks are not
skipped to "active" in the "noirq" resume phase and update the
affected subsystems (PCI and ACPI) accordingly.
After this change, middle-layer (subsystem) callbacks will always
be invoked in all phases of system suspend and resume and driver
callbacks will always run in the prepare, suspend, resume, and
complete phases for all devices.
For devices with SMART_SUSPEND set, driver callbacks will be
skipped in the late and noirq phases of system suspend if those
devices remain in runtime suspend in __device_suspend_late().
Driver callbacks will also be skipped for them during the
noirq and early phases of the "thaw" transition related to
hibernation in that case.
Setting LEAVE_SUSPENDED means that the driver allows its callbacks
to be skipped in the noirq and early phases of system resume, but
some additional conditions need to be met for that to happen (among
other things, the power.may_skip_resume flag needs to be set for the
device during system suspend for the driver callbacks to be skipped
during the subsequent resume transition).
For all devices with SMART_SUSPEND set whose driver callbacks are
invoked during system resume, the PM-runtime status will be set to
"active" (by the core).
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
2020-04-19 00:52:08 +08:00
|
|
|
return 0;
|
|
|
|
|
2018-09-23 21:58:11 +08:00
|
|
|
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;
|
|
|
|
|
2019-07-01 18:54:10 +08:00
|
|
|
/* Follow acpi_subsys_resume_noirq(). */
|
2020-04-19 00:52:30 +08:00
|
|
|
if (dev_pm_skip_resume(dev))
|
2019-07-01 18:54:10 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = pm_generic_resume_noirq(dev);
|
2018-09-23 21:58:11 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2019-07-01 18:54:10 +08:00
|
|
|
if (!pdata->dev_desc->resume_from_noirq)
|
|
|
|
return 0;
|
2018-09-23 21:58:11 +08:00
|
|
|
|
2019-07-01 18:54:10 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
2018-09-23 21:58:11 +08:00
|
|
|
}
|
|
|
|
|
2019-07-01 18:54:10 +08:00
|
|
|
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);
|
2018-09-23 21:58:11 +08:00
|
|
|
}
|
|
|
|
|
2019-07-01 18:54:10 +08:00
|
|
|
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);
|
|
|
|
}
|
2019-07-01 18:54:29 +08:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2020-04-19 00:52:48 +08:00
|
|
|
if (dev_pm_skip_suspend(dev))
|
2019-07-01 18:54:29 +08:00
|
|
|
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));
|
|
|
|
|
2020-04-19 00:52:48 +08:00
|
|
|
if (dev_pm_skip_suspend(dev))
|
2019-07-01 18:54:29 +08:00
|
|
|
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);
|
2021-03-27 20:08:16 +08:00
|
|
|
|
2019-07-01 18:54:29 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pm_generic_poweroff_noirq(dev);
|
|
|
|
}
|
2017-10-16 09:29:55 +08:00
|
|
|
#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)
|
|
|
|
{
|
2018-07-26 16:58:20 +08:00
|
|
|
int ret = acpi_lpss_resume(dev);
|
2017-10-16 09:29:55 +08:00
|
|
|
|
|
|
|
return ret ? ret : pm_generic_runtime_resume(dev);
|
2014-05-23 21:15:09 +08:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
static struct dev_pm_domain acpi_lpss_pm_domain = {
|
2015-12-05 05:49:20 +08:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
.activate = acpi_lpss_activate,
|
|
|
|
.dismiss = acpi_lpss_dismiss,
|
|
|
|
#endif
|
2014-05-23 21:15:09 +08:00
|
|
|
.ops = {
|
2014-11-28 05:38:23 +08:00
|
|
|
#ifdef CONFIG_PM
|
2014-05-23 21:15:09 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
.prepare = acpi_subsys_prepare,
|
2017-10-03 15:11:06 +08:00
|
|
|
.complete = acpi_subsys_complete,
|
2014-05-23 21:15:09 +08:00
|
|
|
.suspend = acpi_subsys_suspend,
|
2014-09-09 22:30:06 +08:00
|
|
|
.suspend_late = acpi_lpss_suspend_late,
|
2018-09-23 21:58:11 +08:00
|
|
|
.suspend_noirq = acpi_lpss_suspend_noirq,
|
|
|
|
.resume_noirq = acpi_lpss_resume_noirq,
|
2014-09-09 22:30:06 +08:00
|
|
|
.resume_early = acpi_lpss_resume_early,
|
2014-05-23 21:15:09 +08:00
|
|
|
.freeze = acpi_subsys_freeze,
|
2019-07-01 18:54:29 +08:00
|
|
|
.poweroff = acpi_subsys_poweroff,
|
|
|
|
.poweroff_late = acpi_lpss_poweroff_late,
|
|
|
|
.poweroff_noirq = acpi_lpss_poweroff_noirq,
|
2019-07-01 18:54:10 +08:00
|
|
|
.restore_noirq = acpi_lpss_restore_noirq,
|
|
|
|
.restore_early = acpi_lpss_restore_early,
|
2014-05-23 21:15:09 +08:00
|
|
|
#endif
|
|
|
|
.runtime_suspend = acpi_lpss_runtime_suspend,
|
|
|
|
.runtime_resume = acpi_lpss_runtime_resume,
|
|
|
|
#endif
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2013-03-07 06:46:28 +08:00
|
|
|
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;
|
|
|
|
|
2021-10-11 01:23:01 +08:00
|
|
|
adev = ACPI_COMPANION(&pdev->dev);
|
|
|
|
if (!adev)
|
2013-03-07 06:46:28 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdata = acpi_driver_data(adev);
|
2014-11-06 00:34:45 +08:00
|
|
|
if (!pdata)
|
2013-03-07 06:46:28 +08:00
|
|
|
return 0;
|
|
|
|
|
2014-11-06 00:34:45 +08:00
|
|
|
if (pdata->mmio_base &&
|
|
|
|
pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) {
|
2013-03-07 06:46:28 +08:00
|
|
|
dev_err(&pdev->dev, "MMIO size insufficient to access LTR\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-23 21:15:09 +08:00
|
|
|
switch (action) {
|
2015-12-05 05:49:19 +08:00
|
|
|
case BUS_NOTIFY_BIND_DRIVER:
|
2016-01-07 23:46:13 +08:00
|
|
|
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
|
2015-12-05 05:49:18 +08:00
|
|
|
break;
|
2015-12-05 05:49:19 +08:00
|
|
|
case BUS_NOTIFY_DRIVER_NOT_BOUND:
|
2015-12-05 05:49:18 +08:00
|
|
|
case BUS_NOTIFY_UNBOUND_DRIVER:
|
2016-02-01 22:17:38 +08:00
|
|
|
dev_pm_domain_set(&pdev->dev, NULL);
|
2015-12-05 05:49:18 +08:00
|
|
|
break;
|
|
|
|
case BUS_NOTIFY_ADD_DEVICE:
|
2016-01-07 23:46:13 +08:00
|
|
|
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
|
2014-09-02 15:55:07 +08:00
|
|
|
if (pdata->dev_desc->flags & LPSS_LTR)
|
2014-05-23 21:15:09 +08:00
|
|
|
return sysfs_create_group(&pdev->dev.kobj,
|
|
|
|
&lpss_attr_group);
|
2014-11-06 00:34:46 +08:00
|
|
|
break;
|
2014-05-23 21:15:09 +08:00
|
|
|
case BUS_NOTIFY_DEL_DEVICE:
|
2014-09-02 15:55:07 +08:00
|
|
|
if (pdata->dev_desc->flags & LPSS_LTR)
|
2014-05-23 21:15:09 +08:00
|
|
|
sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
|
2016-01-07 23:46:13 +08:00
|
|
|
dev_pm_domain_set(&pdev->dev, NULL);
|
2014-11-06 00:34:46 +08:00
|
|
|
break;
|
2014-05-23 21:15:09 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-03-07 06:46:28 +08:00
|
|
|
|
2014-05-23 21:15:09 +08:00
|
|
|
return 0;
|
2013-03-07 06:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block acpi_lpss_nb = {
|
|
|
|
.notifier_call = acpi_lpss_platform_notify,
|
|
|
|
};
|
|
|
|
|
2014-02-11 07:35:53 +08:00
|
|
|
static void acpi_lpss_bind(struct device *dev)
|
|
|
|
{
|
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
|
2014-09-02 15:55:07 +08:00
|
|
|
if (!pdata || !pdata->mmio_base || !(pdata->dev_desc->flags & LPSS_LTR))
|
2014-02-11 07:35:53 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-03-07 06:46:20 +08:00
|
|
|
static struct acpi_scan_handler lpss_handler = {
|
|
|
|
.ids = acpi_lpss_device_ids,
|
|
|
|
.attach = acpi_lpss_create_device,
|
2014-02-11 07:35:53 +08:00
|
|
|
.bind = acpi_lpss_bind,
|
|
|
|
.unbind = acpi_lpss_unbind,
|
2013-03-07 06:46:20 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
void __init acpi_lpss_init(void)
|
|
|
|
{
|
2015-12-12 09:45:06 +08:00
|
|
|
const struct x86_cpu_id *id;
|
|
|
|
int ret;
|
|
|
|
|
2021-07-23 03:34:50 +08:00
|
|
|
ret = lpss_atom_clk_init();
|
2015-12-12 09:45:06 +08:00
|
|
|
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);
|
2013-03-07 06:46:20 +08:00
|
|
|
}
|
2014-05-30 20:34:05 +08:00
|
|
|
|
|
|
|
#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 */
|