2013-03-07 05:46:20 +07:00
|
|
|
/*
|
|
|
|
* ACPI support for Intel Lynxpoint LPSS.
|
|
|
|
*
|
2015-02-03 20:29:43 +07:00
|
|
|
* Copyright (C) 2013, Intel Corporation
|
2013-03-07 05:46:20 +07:00
|
|
|
* Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
|
|
|
|
* Rafael J. Wysocki <rafael.j.wysocki@intel.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/clkdev.h>
|
|
|
|
#include <linux/clk-provider.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/io.h>
|
2015-12-12 08:45:06 +07:00
|
|
|
#include <linux/mutex.h>
|
2018-09-23 20:58:08 +07:00
|
|
|
#include <linux/pci.h>
|
2013-03-07 05:46:20 +07:00
|
|
|
#include <linux/platform_device.h>
|
2019-02-19 02:34:15 +07:00
|
|
|
#include <linux/platform_data/x86/clk-lpss.h>
|
2017-01-24 01:07:43 +07:00
|
|
|
#include <linux/platform_data/x86/pmc_atom.h>
|
2016-01-07 22:46:13 +07:00
|
|
|
#include <linux/pm_domain.h>
|
2013-03-07 05:46:28 +07:00
|
|
|
#include <linux/pm_runtime.h>
|
2017-01-22 23:14:09 +07:00
|
|
|
#include <linux/pwm.h>
|
2018-06-13 18:17:26 +07:00
|
|
|
#include <linux/suspend.h>
|
2014-05-23 20:15:09 +07:00
|
|
|
#include <linux/delay.h>
|
2013-03-07 05:46:20 +07:00
|
|
|
|
|
|
|
#include "internal.h"
|
|
|
|
|
|
|
|
ACPI_MODULE_NAME("acpi_lpss");
|
|
|
|
|
2014-05-30 19:34:05 +07:00
|
|
|
#ifdef CONFIG_X86_INTEL_LPSS
|
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
#include <asm/cpu_device_id.h>
|
2016-06-03 07:19:46 +07:00
|
|
|
#include <asm/intel-family.h>
|
2015-12-12 08:45:06 +07:00
|
|
|
#include <asm/iosf_mbi.h>
|
|
|
|
|
2014-05-30 19:34:05 +07:00
|
|
|
#define LPSS_ADDR(desc) ((unsigned long)&desc)
|
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
#define LPSS_CLK_SIZE 0x04
|
2013-03-07 05:46:28 +07:00
|
|
|
#define LPSS_LTR_SIZE 0x18
|
|
|
|
|
|
|
|
/* Offsets relative to LPSS_PRIVATE_OFFSET */
|
2014-05-19 18:42:07 +07:00
|
|
|
#define LPSS_CLK_DIVIDER_DEF_MASK (BIT(1) | BIT(16))
|
2014-06-17 18:33:39 +07:00
|
|
|
#define LPSS_RESETS 0x04
|
|
|
|
#define LPSS_RESETS_RESET_FUNC BIT(0)
|
|
|
|
#define LPSS_RESETS_RESET_APB BIT(1)
|
2013-03-07 05:46:28 +07:00
|
|
|
#define LPSS_GENERAL 0x08
|
|
|
|
#define LPSS_GENERAL_LTR_MODE_SW BIT(2)
|
2013-10-09 13:49:20 +07:00
|
|
|
#define LPSS_GENERAL_UART_RTS_OVRD BIT(3)
|
2013-03-07 05:46:28 +07:00
|
|
|
#define LPSS_SW_LTR 0x10
|
|
|
|
#define LPSS_AUTO_LTR 0x14
|
2014-02-11 06:35:53 +07: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 17:25:46 +07:00
|
|
|
#define LPSS_TX_INT 0x20
|
|
|
|
#define LPSS_TX_INT_MASK BIT(1)
|
2013-03-07 05:46:20 +07:00
|
|
|
|
2014-05-23 20:15:09 +07:00
|
|
|
#define LPSS_PRV_REG_COUNT 9
|
|
|
|
|
2014-09-02 14:55:07 +07: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)
|
2015-08-27 23:00:55 +07:00
|
|
|
#define LPSS_NO_D3_DELAY BIT(5)
|
2013-05-13 19:42:44 +07:00
|
|
|
|
ACPI / LPSS: Only call pwm_add_table() for Bay Trail PWM if PMIC HRV is 2
The Point of View mobii wintab p800w Bay Trail tablet comes with a Crystal
Cove PMIC, yet uses the LPSS PWM for backlight control, rather then the
Crystal Cove's PWM, so we need to call pwm_add_table() to add a
pwm_backlight mapping for the LPSS pwm despite there being an INT33FD
ACPI device present.
On all Bay Trail devices the _HRV object of the INT33FD ACPI device
will normally return 2, to indicate the Bay Trail variant of the CRC
PMIC is present, except on this tablet where _HRV is 0xffff. I guess this
is a hack to make the windows Crystal Cove PWM driver not bind.
Out of the 44 DSTDs with an INT33FD device in there which I have (from
different model devices) only the pov mobii wintab p800w uses 0xffff for
the HRV.
The byt_pwm_setup code calls acpi_dev_present to check for the presence
of a INT33FD ACPI device which indicates that a CRC PMIC is present and
if the INT33FD ACPI device is present then byt_pwm_setup will not add
a pwm_backlight mapping for the LPSS pwm, so that the CRC PWM will get
used instead.
acpi_dev_present has a hrv parameter, this commit make us pass 2 instead
of -1, so that things still match on normal tablets, but on this special
case with its _HRV of 0xffff, the check will now fail so that the
pwm_backlight mapping for the LPSS pwm gets added fixing backlight
brightness control on this device.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2018-04-13 19:54:17 +07:00
|
|
|
/* Crystal Cove PMIC shares same ACPI ID between different platforms */
|
|
|
|
#define BYT_CRC_HRV 2
|
|
|
|
#define CHT_CRC_HRV 3
|
|
|
|
|
2013-06-17 17:25:46 +07:00
|
|
|
struct lpss_private_data;
|
2013-03-07 05:46:20 +07:00
|
|
|
|
|
|
|
struct lpss_device_desc {
|
2014-09-02 14:55:07 +07:00
|
|
|
unsigned int flags;
|
2015-03-06 20:48:38 +07:00
|
|
|
const char *clk_con_id;
|
2013-03-07 05:46:28 +07:00
|
|
|
unsigned int prv_offset;
|
2013-06-18 20:51:35 +07:00
|
|
|
size_t prv_size_override;
|
2016-08-23 15:33:27 +07:00
|
|
|
struct property_entry *properties;
|
2013-06-17 17:25:46 +07:00
|
|
|
void (*setup)(struct lpss_private_data *pdata);
|
2018-09-23 20:58:11 +07:00
|
|
|
bool resume_from_noirq;
|
2013-03-07 05:46:20 +07:00
|
|
|
};
|
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
static const struct lpss_device_desc lpss_dma_desc = {
|
2015-02-03 20:29:43 +07:00
|
|
|
.flags = LPSS_CLK,
|
2013-05-08 15:55:49 +07:00
|
|
|
};
|
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
struct lpss_private_data {
|
2017-07-06 23:49:27 +07:00
|
|
|
struct acpi_device *adev;
|
2013-03-07 05:46:20 +07:00
|
|
|
void __iomem *mmio_base;
|
|
|
|
resource_size_t mmio_size;
|
2014-09-02 14:55:09 +07:00
|
|
|
unsigned int fixed_clk_rate;
|
2013-03-07 05:46:20 +07:00
|
|
|
struct clk *clk;
|
|
|
|
const struct lpss_device_desc *dev_desc;
|
2014-05-23 20:15:09 +07:00
|
|
|
u32 prv_reg_ctx[LPSS_PRV_REG_COUNT];
|
2013-03-07 05:46:20 +07:00
|
|
|
};
|
|
|
|
|
2018-09-09 01:08:13 +07: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 08:45:06 +07: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 03:31:09 +07:00
|
|
|
* The LPSS DMA controller has neither _PS0 nor _PS3 method. Moreover
|
2015-12-12 08:45:06 +07: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 19:19:33 +07:00
|
|
|
/* UART Component Parameter Register */
|
|
|
|
#define LPSS_UART_CPR 0xF4
|
|
|
|
#define LPSS_UART_CPR_AFCE BIT(4)
|
|
|
|
|
2013-06-17 17:25:46 +07:00
|
|
|
static void lpss_uart_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2013-10-09 13:49:20 +07:00
|
|
|
unsigned int offset;
|
2014-09-11 19:19:33 +07:00
|
|
|
u32 val;
|
2013-06-17 17:25:46 +07:00
|
|
|
|
2013-10-09 13:49:20 +07:00
|
|
|
offset = pdata->dev_desc->prv_offset + LPSS_TX_INT;
|
2014-09-11 19:19:33 +07: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 17:25:46 +07:00
|
|
|
}
|
|
|
|
|
2015-02-18 18:50:17 +07:00
|
|
|
static void lpss_deassert_reset(struct lpss_private_data *pdata)
|
2014-06-17 18:33:39 +07: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 18:50:17 +07:00
|
|
|
}
|
|
|
|
|
2017-04-21 14:35:07 +07: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",
|
|
|
|
"pwm_backlight", 0, PWM_POLARITY_NORMAL,
|
|
|
|
"pwm-lpss-platform"),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void byt_pwm_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2017-07-06 23:49:27 +07:00
|
|
|
struct acpi_device *adev = pdata->adev;
|
|
|
|
|
|
|
|
/* Only call pwm_add_table for the first PWM controller */
|
|
|
|
if (!adev->pnp.unique_id || strcmp(adev->pnp.unique_id, "1"))
|
|
|
|
return;
|
|
|
|
|
ACPI / LPSS: Only call pwm_add_table() for Bay Trail PWM if PMIC HRV is 2
The Point of View mobii wintab p800w Bay Trail tablet comes with a Crystal
Cove PMIC, yet uses the LPSS PWM for backlight control, rather then the
Crystal Cove's PWM, so we need to call pwm_add_table() to add a
pwm_backlight mapping for the LPSS pwm despite there being an INT33FD
ACPI device present.
On all Bay Trail devices the _HRV object of the INT33FD ACPI device
will normally return 2, to indicate the Bay Trail variant of the CRC
PMIC is present, except on this tablet where _HRV is 0xffff. I guess this
is a hack to make the windows Crystal Cove PWM driver not bind.
Out of the 44 DSTDs with an INT33FD device in there which I have (from
different model devices) only the pov mobii wintab p800w uses 0xffff for
the HRV.
The byt_pwm_setup code calls acpi_dev_present to check for the presence
of a INT33FD ACPI device which indicates that a CRC PMIC is present and
if the INT33FD ACPI device is present then byt_pwm_setup will not add
a pwm_backlight mapping for the LPSS pwm, so that the CRC PWM will get
used instead.
acpi_dev_present has a hrv parameter, this commit make us pass 2 instead
of -1, so that things still match on normal tablets, but on this special
case with its _HRV of 0xffff, the check will now fail so that the
pwm_backlight mapping for the LPSS pwm gets added fixing backlight
brightness control on this device.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2018-04-13 19:54:17 +07:00
|
|
|
if (!acpi_dev_present("INT33FD", NULL, BYT_CRC_HRV))
|
2017-04-21 14:35:07 +07:00
|
|
|
pwm_add_table(byt_pwm_lookup, ARRAY_SIZE(byt_pwm_lookup));
|
|
|
|
}
|
|
|
|
|
2015-02-18 18:50:17 +07:00
|
|
|
#define LPSS_I2C_ENABLE 0x6c
|
|
|
|
|
|
|
|
static void byt_i2c_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2018-09-09 01:08:13 +07:00
|
|
|
const char *uid_str = acpi_device_uid(pdata->adev);
|
|
|
|
acpi_handle handle = pdata->adev->handle;
|
|
|
|
unsigned long long shared_host = 0;
|
|
|
|
acpi_status status;
|
|
|
|
long uid = 0;
|
|
|
|
|
|
|
|
/* Expected to always be true, but better safe then sorry */
|
|
|
|
if (uid_str)
|
|
|
|
uid = simple_strtol(uid_str, NULL, 10);
|
|
|
|
|
|
|
|
/* 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 && uid)
|
|
|
|
pmc_atom_d3_mask &= ~(BIT_LPSS2_F1_I2C1 << (uid - 1));
|
|
|
|
|
2015-02-18 18:50:17 +07:00
|
|
|
lpss_deassert_reset(pdata);
|
2014-06-17 18:33:39 +07:00
|
|
|
|
2014-09-02 14:55:09 +07:00
|
|
|
if (readl(pdata->mmio_base + pdata->dev_desc->prv_offset))
|
|
|
|
pdata->fixed_clk_rate = 133000000;
|
2015-02-18 18:50:16 +07:00
|
|
|
|
|
|
|
writel(0, pdata->mmio_base + LPSS_I2C_ENABLE);
|
2014-06-17 18:33:39 +07:00
|
|
|
}
|
2014-08-01 08:06:35 +07:00
|
|
|
|
2017-01-22 23:14:09 +07: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",
|
|
|
|
"pwm_backlight", 0, PWM_POLARITY_NORMAL,
|
|
|
|
"pwm-lpss-platform"),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void bsw_pwm_setup(struct lpss_private_data *pdata)
|
|
|
|
{
|
2017-07-06 23:49:27 +07:00
|
|
|
struct acpi_device *adev = pdata->adev;
|
|
|
|
|
|
|
|
/* Only call pwm_add_table for the first PWM controller */
|
|
|
|
if (!adev->pnp.unique_id || strcmp(adev->pnp.unique_id, "1"))
|
|
|
|
return;
|
|
|
|
|
2017-01-22 23:14:09 +07:00
|
|
|
pwm_add_table(bsw_pwm_lookup, ARRAY_SIZE(bsw_pwm_lookup));
|
|
|
|
}
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc lpt_dev_desc = {
|
2014-09-02 14:55:07 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR,
|
2014-05-19 18:42:07 +07:00
|
|
|
.prv_offset = 0x800,
|
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc lpt_i2c_dev_desc = {
|
2014-09-02 14:55:07 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_LTR,
|
2013-03-07 05:46:28 +07:00
|
|
|
.prv_offset = 0x800,
|
|
|
|
};
|
|
|
|
|
2016-08-23 15:33:27 +07: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 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc lpt_uart_dev_desc = {
|
2014-09-02 14:55:07 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_LTR,
|
2015-03-06 20:48:38 +07:00
|
|
|
.clk_con_id = "baudclk",
|
2013-06-17 17:25:46 +07:00
|
|
|
.prv_offset = 0x800,
|
|
|
|
.setup = lpss_uart_setup,
|
2016-08-23 15:33:27 +07:00
|
|
|
.properties = uart_properties,
|
2013-03-07 05:46:28 +07:00
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc lpt_sdio_dev_desc = {
|
2014-09-02 14:55:07 +07:00
|
|
|
.flags = LPSS_LTR,
|
2013-03-07 05:46:28 +07:00
|
|
|
.prv_offset = 0x1000,
|
2013-06-18 20:51:35 +07:00
|
|
|
.prv_size_override = 0x1018,
|
2014-02-19 01:24:29 +07:00
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc byt_pwm_dev_desc = {
|
2014-09-02 14:55:10 +07:00
|
|
|
.flags = LPSS_SAVE_CTX,
|
2018-04-26 19:10:24 +07:00
|
|
|
.prv_offset = 0x800,
|
2017-04-21 14:35:07 +07:00
|
|
|
.setup = byt_pwm_setup,
|
2014-02-19 01:24:29 +07:00
|
|
|
};
|
|
|
|
|
2015-08-27 23:00:55 +07:00
|
|
|
static const struct lpss_device_desc bsw_pwm_dev_desc = {
|
|
|
|
.flags = LPSS_SAVE_CTX | LPSS_NO_D3_DELAY,
|
2018-04-26 19:10:24 +07:00
|
|
|
.prv_offset = 0x800,
|
2017-01-22 23:14:09 +07:00
|
|
|
.setup = bsw_pwm_setup,
|
2015-08-27 23:00:55 +07:00
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc byt_uart_dev_desc = {
|
2015-02-03 20:29:43 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX,
|
2015-03-06 20:48:38 +07:00
|
|
|
.clk_con_id = "baudclk",
|
2013-05-13 19:42:44 +07:00
|
|
|
.prv_offset = 0x800,
|
2013-06-17 17:25:46 +07:00
|
|
|
.setup = lpss_uart_setup,
|
2016-08-23 15:33:27 +07:00
|
|
|
.properties = uart_properties,
|
2013-05-13 19:42:44 +07:00
|
|
|
};
|
|
|
|
|
2015-08-27 23:00:55 +07: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 15:33:27 +07:00
|
|
|
.properties = uart_properties,
|
2015-08-27 23:00:55 +07:00
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc byt_spi_dev_desc = {
|
2015-02-03 20:29:43 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX,
|
2013-05-13 19:42:44 +07:00
|
|
|
.prv_offset = 0x400,
|
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc byt_sdio_dev_desc = {
|
2015-02-03 20:29:43 +07:00
|
|
|
.flags = LPSS_CLK,
|
2013-05-13 19:42:44 +07:00
|
|
|
};
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
static const struct lpss_device_desc byt_i2c_dev_desc = {
|
2015-02-03 20:29:43 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_SAVE_CTX,
|
2013-05-13 19:42:44 +07:00
|
|
|
.prv_offset = 0x800,
|
2014-09-02 14:55:09 +07:00
|
|
|
.setup = byt_i2c_setup,
|
2018-09-23 20:58:11 +07:00
|
|
|
.resume_from_noirq = true,
|
2014-08-19 19:55:22 +07:00
|
|
|
};
|
|
|
|
|
2015-08-27 23:00:55 +07: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 20:58:11 +07:00
|
|
|
.resume_from_noirq = true,
|
2015-08-27 23:00:55 +07:00
|
|
|
};
|
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
static const struct lpss_device_desc bsw_spi_dev_desc = {
|
2015-08-27 23:00:55 +07:00
|
|
|
.flags = LPSS_CLK | LPSS_CLK_GATE | LPSS_CLK_DIVIDER | LPSS_SAVE_CTX
|
|
|
|
| LPSS_NO_D3_DELAY,
|
2015-02-18 18:50:17 +07:00
|
|
|
.prv_offset = 0x400,
|
|
|
|
.setup = lpss_deassert_reset,
|
|
|
|
};
|
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, }
|
|
|
|
|
|
|
|
static const struct x86_cpu_id lpss_cpu_ids[] = {
|
2018-08-08 00:17:27 +07:00
|
|
|
ICPU(INTEL_FAM6_ATOM_SILVERMONT), /* Valleyview, Bay Trail */
|
2016-06-03 07:19:46 +07:00
|
|
|
ICPU(INTEL_FAM6_ATOM_AIRMONT), /* Braswell, Cherry Trail */
|
2015-12-12 08:45:06 +07:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
2014-05-30 19:34:05 +07:00
|
|
|
#else
|
|
|
|
|
|
|
|
#define LPSS_ADDR(desc) (0UL)
|
|
|
|
|
|
|
|
#endif /* CONFIG_X86_INTEL_LPSS */
|
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
static const struct acpi_device_id acpi_lpss_device_ids[] = {
|
2013-05-08 15:55:49 +07:00
|
|
|
/* Generic LPSS devices */
|
2014-05-30 19:34:05 +07:00
|
|
|
{ "INTL9C60", LPSS_ADDR(lpss_dma_desc) },
|
2013-05-08 15:55:49 +07:00
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
/* Lynxpoint LPSS devices */
|
2014-05-30 19:34:05 +07:00
|
|
|
{ "INT33C0", LPSS_ADDR(lpt_dev_desc) },
|
|
|
|
{ "INT33C1", LPSS_ADDR(lpt_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) },
|
2013-03-07 05:46:20 +07:00
|
|
|
{ "INT33C7", },
|
|
|
|
|
2013-05-13 19:42:44 +07:00
|
|
|
/* BayTrail LPSS devices */
|
2014-05-30 19:34:05 +07: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 19:42:44 +07:00
|
|
|
{ "INT33B2", },
|
2014-05-15 22:28:46 +07:00
|
|
|
{ "INT33FC", },
|
2013-05-13 19:42:44 +07:00
|
|
|
|
2014-08-19 19:55:22 +07:00
|
|
|
/* Braswell LPSS devices */
|
2018-08-27 14:45:44 +07:00
|
|
|
{ "80862286", LPSS_ADDR(lpss_dma_desc) },
|
2015-08-27 23:00:55 +07:00
|
|
|
{ "80862288", LPSS_ADDR(bsw_pwm_dev_desc) },
|
|
|
|
{ "8086228A", LPSS_ADDR(bsw_uart_dev_desc) },
|
2015-02-18 18:50:17 +07:00
|
|
|
{ "8086228E", LPSS_ADDR(bsw_spi_dev_desc) },
|
2018-08-27 14:45:44 +07:00
|
|
|
{ "808622C0", LPSS_ADDR(lpss_dma_desc) },
|
2015-08-27 23:00:55 +07:00
|
|
|
{ "808622C1", LPSS_ADDR(bsw_i2c_dev_desc) },
|
2014-08-19 19:55:22 +07:00
|
|
|
|
2015-08-27 23:00:55 +07:00
|
|
|
/* Broadwell LPSS devices */
|
2014-05-30 19:34:05 +07:00
|
|
|
{ "INT3430", LPSS_ADDR(lpt_dev_desc) },
|
|
|
|
{ "INT3431", LPSS_ADDR(lpt_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) },
|
2013-11-12 16:48:19 +07:00
|
|
|
{ "INT3437", },
|
|
|
|
|
2014-09-02 14:55:07 +07:00
|
|
|
/* Wildcat Point LPSS devices */
|
|
|
|
{ "INT3438", LPSS_ADDR(lpt_dev_desc) },
|
2014-08-01 08:06:35 +07:00
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2014-05-30 19:34:05 +07:00
|
|
|
#ifdef CONFIG_X86_INTEL_LPSS
|
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
static int is_memory(struct acpi_resource *res, void *not_used)
|
|
|
|
{
|
|
|
|
struct resource r;
|
|
|
|
return !acpi_dev_resource_memory(res, &r);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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-lpt", -1, 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;
|
2014-05-19 18:42:07 +07:00
|
|
|
const char *devname = dev_name(&adev->dev);
|
2017-10-15 18:50:34 +07:00
|
|
|
struct clk *clk;
|
2013-05-08 15:55:49 +07:00
|
|
|
struct lpss_clk_data *clk_data;
|
2014-05-19 18:42:07 +07:00
|
|
|
const char *parent, *clk_name;
|
|
|
|
void __iomem *prv_base;
|
2013-03-07 05:46:20 +07:00
|
|
|
|
|
|
|
if (!lpss_clk_dev)
|
|
|
|
lpt_register_clock_device();
|
|
|
|
|
2013-05-08 15:55:49 +07:00
|
|
|
clk_data = platform_get_drvdata(lpss_clk_dev);
|
|
|
|
if (!clk_data)
|
|
|
|
return -ENODEV;
|
2014-09-02 14:55:08 +07:00
|
|
|
clk = clk_data->clk;
|
2013-05-08 15:55:49 +07:00
|
|
|
|
|
|
|
if (!pdata->mmio_base
|
2013-03-07 05:46:28 +07:00
|
|
|
|| pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE)
|
2013-03-07 05:46:20 +07:00
|
|
|
return -ENODATA;
|
|
|
|
|
2013-05-13 19:42:44 +07:00
|
|
|
parent = clk_data->name;
|
2014-05-19 18:42:07 +07:00
|
|
|
prv_base = pdata->mmio_base + dev_desc->prv_offset;
|
2013-05-13 19:42:44 +07:00
|
|
|
|
2014-09-02 14:55:09 +07:00
|
|
|
if (pdata->fixed_clk_rate) {
|
|
|
|
clk = clk_register_fixed_rate(NULL, devname, parent, 0,
|
|
|
|
pdata->fixed_clk_rate);
|
|
|
|
goto out;
|
2013-05-13 19:42:44 +07:00
|
|
|
}
|
|
|
|
|
2014-09-02 14:55:07 +07:00
|
|
|
if (dev_desc->flags & LPSS_CLK_GATE) {
|
2014-05-19 18:42:07 +07:00
|
|
|
clk = clk_register_gate(NULL, devname, parent, 0,
|
|
|
|
prv_base, 0, 0, NULL);
|
|
|
|
parent = devname;
|
|
|
|
}
|
|
|
|
|
2014-09-02 14:55:07 +07:00
|
|
|
if (dev_desc->flags & LPSS_CLK_DIVIDER) {
|
2014-05-19 18:42:07 +07: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,
|
|
|
|
0, prv_base,
|
|
|
|
1, 15, 16, 15, 0, 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);
|
2013-05-13 19:42:44 +07:00
|
|
|
}
|
2014-09-02 14:55:09 +07:00
|
|
|
out:
|
2013-05-13 19:42:44 +07:00
|
|
|
if (IS_ERR(clk))
|
|
|
|
return PTR_ERR(clk);
|
2013-03-07 05:46:20 +07:00
|
|
|
|
2014-05-19 18:42:07 +07:00
|
|
|
pdata->clk = clk;
|
2015-03-06 20:48:38 +07:00
|
|
|
clk_register_clkdev(clk, dev_desc->clk_con_id, devname);
|
2013-03-07 05:46:20 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-15 19:48:47 +07:00
|
|
|
struct lpss_device_links {
|
|
|
|
const char *supplier_hid;
|
|
|
|
const char *supplier_uid;
|
|
|
|
const char *consumer_hid;
|
|
|
|
const char *consumer_uid;
|
|
|
|
u32 flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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[] = {
|
|
|
|
{"808622C1", "7", "80860F14", "3", DL_FLAG_PM_RUNTIME},
|
2018-09-23 20:58:09 +07:00
|
|
|
{"808622C1", "7", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME},
|
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 20:58:10 +07:00
|
|
|
{"80860F41", "5", "LNXVIDEO", NULL, DL_FLAG_PM_RUNTIME},
|
2017-12-15 19:48:47 +07:00
|
|
|
};
|
|
|
|
|
2018-09-23 20:58:06 +07:00
|
|
|
static bool hid_uid_match(struct acpi_device *adev,
|
2017-12-15 19:48:47 +07:00
|
|
|
const char *hid2, const char *uid2)
|
|
|
|
{
|
2018-09-23 20:58:06 +07:00
|
|
|
const char *hid1 = acpi_device_hid(adev);
|
|
|
|
const char *uid1 = acpi_device_uid(adev);
|
|
|
|
|
2018-09-23 20:58:07 +07:00
|
|
|
if (strcmp(hid1, hid2))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!uid2)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return uid1 && !strcmp(uid1, uid2);
|
2017-12-15 19:48:47 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool acpi_lpss_is_supplier(struct acpi_device *adev,
|
|
|
|
const struct lpss_device_links *link)
|
|
|
|
{
|
2018-09-23 20:58:06 +07:00
|
|
|
return hid_uid_match(adev, link->supplier_hid, link->supplier_uid);
|
2017-12-15 19:48:47 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool acpi_lpss_is_consumer(struct acpi_device *adev,
|
|
|
|
const struct lpss_device_links *link)
|
|
|
|
{
|
2018-09-23 20:58:06 +07:00
|
|
|
return hid_uid_match(adev, link->consumer_hid, link->consumer_uid);
|
2017-12-15 19:48:47 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
struct hid_uid {
|
|
|
|
const char *hid;
|
|
|
|
const char *uid;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int match_hid_uid(struct device *dev, void *data)
|
|
|
|
{
|
|
|
|
struct acpi_device *adev = ACPI_COMPANION(dev);
|
|
|
|
struct hid_uid *id = data;
|
|
|
|
|
|
|
|
if (!adev)
|
|
|
|
return 0;
|
|
|
|
|
2018-09-23 20:58:06 +07:00
|
|
|
return hid_uid_match(adev, id->hid, id->uid);
|
2017-12-15 19:48:47 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct device *acpi_lpss_find_device(const char *hid, const char *uid)
|
|
|
|
{
|
2018-09-23 20:58:08 +07:00
|
|
|
struct device *dev;
|
|
|
|
|
2017-12-15 19:48:47 +07:00
|
|
|
struct hid_uid data = {
|
|
|
|
.hid = hid,
|
|
|
|
.uid = uid,
|
|
|
|
};
|
|
|
|
|
2018-09-23 20:58:08 +07: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 19:48:47 +07: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;
|
|
|
|
|
|
|
|
if (acpi_lpss_dep(ACPI_COMPANION(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 (acpi_lpss_dep(ACPI_COMPANION(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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
static int acpi_lpss_create_device(struct acpi_device *adev,
|
|
|
|
const struct acpi_device_id *id)
|
|
|
|
{
|
2015-06-13 19:26:50 +07:00
|
|
|
const struct lpss_device_desc *dev_desc;
|
2013-03-07 05:46:20 +07:00
|
|
|
struct lpss_private_data *pdata;
|
2015-02-05 12:44:43 +07:00
|
|
|
struct resource_entry *rentry;
|
2013-03-07 05:46:20 +07:00
|
|
|
struct list_head resource_list;
|
2014-05-25 19:38:52 +07:00
|
|
|
struct platform_device *pdev;
|
2013-03-07 05:46:20 +07:00
|
|
|
int ret;
|
|
|
|
|
2015-06-13 19:26:50 +07:00
|
|
|
dev_desc = (const struct lpss_device_desc *)id->driver_data;
|
2014-05-25 19:38:52 +07:00
|
|
|
if (!dev_desc) {
|
2016-11-03 21:21:26 +07:00
|
|
|
pdev = acpi_create_platform_device(adev, NULL);
|
2014-05-25 19:38:52 +07:00
|
|
|
return IS_ERR_OR_NULL(pdev) ? PTR_ERR(pdev) : 1;
|
|
|
|
}
|
2013-03-07 05:46:20 +07:00
|
|
|
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
|
|
|
if (!pdata)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&resource_list);
|
|
|
|
ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
list_for_each_entry(rentry, &resource_list, node)
|
2015-02-05 12:44:43 +07:00
|
|
|
if (resource_type(rentry->res) == IORESOURCE_MEM) {
|
2013-06-18 20:51:35 +07:00
|
|
|
if (dev_desc->prv_size_override)
|
|
|
|
pdata->mmio_size = dev_desc->prv_size_override;
|
|
|
|
else
|
2015-02-05 12:44:43 +07:00
|
|
|
pdata->mmio_size = resource_size(rentry->res);
|
|
|
|
pdata->mmio_base = ioremap(rentry->res->start,
|
2013-03-07 05:46:20 +07:00
|
|
|
pdata->mmio_size);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
acpi_dev_free_resource_list(&resource_list);
|
|
|
|
|
2015-07-07 05:31:47 +07:00
|
|
|
if (!pdata->mmio_base) {
|
2018-01-15 03:01:48 +07:00
|
|
|
/* Avoid acpi_bus_attach() instantiating a pdev for this dev. */
|
|
|
|
adev->pnp.type.platform_id = 0;
|
2017-08-09 15:15:42 +07:00
|
|
|
/* Skip the device, but continue the namespace scan. */
|
|
|
|
ret = 0;
|
2015-07-07 05:31:47 +07:00
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
2017-07-06 23:49:27 +07:00
|
|
|
pdata->adev = adev;
|
2013-09-02 17:30:25 +07:00
|
|
|
pdata->dev_desc = dev_desc;
|
|
|
|
|
2014-09-02 14:55:09 +07:00
|
|
|
if (dev_desc->setup)
|
|
|
|
dev_desc->setup(pdata);
|
|
|
|
|
2014-09-02 14:55:07 +07:00
|
|
|
if (dev_desc->flags & LPSS_CLK) {
|
2013-03-07 05:46:20 +07:00
|
|
|
ret = register_device_clock(adev, pdata);
|
|
|
|
if (ret) {
|
2013-06-19 05:45:34 +07:00
|
|
|
/* Skip the device, but continue the namespace scan. */
|
|
|
|
ret = 0;
|
|
|
|
goto err_out;
|
2013-03-07 05:46:20 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-19 05:45:34 +07: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 19:59:24 +07:00
|
|
|
acpi_device_fix_up_power(adev);
|
2013-06-19 05:45:34 +07:00
|
|
|
|
2013-03-07 05:46:20 +07:00
|
|
|
adev->driver_data = pdata;
|
2016-11-03 21:21:26 +07:00
|
|
|
pdev = acpi_create_platform_device(adev, dev_desc->properties);
|
2014-05-25 19:38:52 +07:00
|
|
|
if (!IS_ERR_OR_NULL(pdev)) {
|
2017-12-15 19:48:47 +07:00
|
|
|
acpi_lpss_create_device_links(adev, pdev);
|
2014-05-25 19:38:52 +07:00
|
|
|
return 1;
|
|
|
|
}
|
2013-03-07 05:46:20 +07:00
|
|
|
|
2014-05-25 19:38:52 +07:00
|
|
|
ret = PTR_ERR(pdev);
|
2013-03-07 05:46:20 +07:00
|
|
|
adev->driver_data = NULL;
|
|
|
|
|
|
|
|
err_out:
|
|
|
|
kfree(pdata);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-02-11 06:35:53 +07: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 05:46:28 +07:00
|
|
|
static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val)
|
|
|
|
{
|
|
|
|
struct acpi_device *adev;
|
|
|
|
struct lpss_private_data *pdata;
|
|
|
|
unsigned long flags;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = acpi_bus_get_device(ACPI_HANDLE(dev), &adev);
|
|
|
|
if (WARN_ON(ret))
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
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 06:35:53 +07:00
|
|
|
*val = __lpss_reg_read(pdata, reg);
|
2013-03-07 05:46:28 +07: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;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%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,
|
|
|
|
};
|
|
|
|
|
2017-06-30 19:53:50 +07:00
|
|
|
static const struct attribute_group lpss_attr_group = {
|
2013-03-07 05:46:28 +07:00
|
|
|
.attrs = lpss_attrs,
|
|
|
|
.name = "lpss_ltr",
|
|
|
|
};
|
|
|
|
|
2014-02-11 06:35:53 +07: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 20:15:09 +07:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
/**
|
|
|
|
* acpi_lpss_save_ctx() - Save the private registers of LPSS device
|
|
|
|
* @dev: LPSS device
|
2014-11-05 23:34:45 +07:00
|
|
|
* @pdata: pointer to the private data of the LPSS device
|
2014-05-23 20:15:09 +07: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-05 23:34:45 +07:00
|
|
|
static void acpi_lpss_save_ctx(struct device *dev,
|
|
|
|
struct lpss_private_data *pdata)
|
2014-05-23 20:15:09 +07: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-05 23:34:45 +07:00
|
|
|
* @pdata: pointer to the private data of the LPSS device
|
2014-05-23 20:15:09 +07:00
|
|
|
*
|
|
|
|
* Restores the registers that were previously stored with acpi_lpss_save_ctx().
|
|
|
|
*/
|
2014-11-05 23:34:45 +07:00
|
|
|
static void acpi_lpss_restore_ctx(struct device *dev,
|
|
|
|
struct lpss_private_data *pdata)
|
2014-05-23 20:15:09 +07:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
2015-12-05 04:49:21 +07: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 20:15:09 +07: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-27 23:00:55 +07:00
|
|
|
* transition. However some platforms like BSW does not need this delay.
|
2014-05-23 20:15:09 +07:00
|
|
|
*/
|
2015-08-27 23:00:55 +07:00
|
|
|
unsigned int delay = 10; /* default 10ms delay */
|
|
|
|
|
|
|
|
if (pdata->dev_desc->flags & LPSS_NO_D3_DELAY)
|
|
|
|
delay = 0;
|
|
|
|
|
|
|
|
msleep(delay);
|
2014-05-23 20:15:09 +07:00
|
|
|
}
|
|
|
|
|
2015-12-05 04:49:20 +07:00
|
|
|
static int acpi_lpss_activate(struct device *dev)
|
|
|
|
{
|
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
|
|
|
|
2017-10-10 23:49:22 +07:00
|
|
|
ret = acpi_dev_resume(dev);
|
2015-12-05 04:49:20 +07: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.
|
|
|
|
*/
|
|
|
|
if (pdata->dev_desc->flags & LPSS_SAVE_CTX)
|
|
|
|
lpss_deassert_reset(pdata);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void acpi_lpss_dismiss(struct device *dev)
|
|
|
|
{
|
2017-10-14 22:43:15 +07:00
|
|
|
acpi_dev_suspend(dev, false);
|
2015-12-05 04:49:20 +07:00
|
|
|
}
|
|
|
|
|
2015-12-12 08:45:06 +07: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 21:30:06 +07:00
|
|
|
#define LPSS_GPIODEF0_DMA_LLP BIT(13)
|
2015-12-12 08:45:06 +07:00
|
|
|
|
|
|
|
static DEFINE_MUTEX(lpss_iosf_mutex);
|
2018-09-03 09:00:07 +07:00
|
|
|
static bool lpss_iosf_d3_entered = true;
|
2015-12-12 08:45:06 +07:00
|
|
|
|
|
|
|
static void lpss_iosf_enter_d3_state(void)
|
|
|
|
{
|
|
|
|
u32 value1 = 0;
|
2016-11-17 21:30:06 +07:00
|
|
|
u32 mask1 = LPSS_GPIODEF0_DMA_D3_MASK | LPSS_GPIODEF0_DMA_LLP;
|
2015-12-12 08:45:06 +07: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 01:08:13 +07:00
|
|
|
u32 func_dis, d3_sts_0, pmc_status;
|
2015-12-12 08:45:06 +07: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 01:08:13 +07:00
|
|
|
pmc_status = (~(d3_sts_0 | func_dis)) & pmc_atom_d3_mask;
|
2015-12-12 08:45:06 +07: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 15:58:20 +07:00
|
|
|
|
|
|
|
lpss_iosf_d3_entered = true;
|
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
exit:
|
|
|
|
mutex_unlock(&lpss_iosf_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lpss_iosf_exit_d3_state(void)
|
|
|
|
{
|
2016-11-17 21:30:06 +07: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 08:45:06 +07:00
|
|
|
u32 value2 = LPSS_PMCSR_D0;
|
|
|
|
u32 mask2 = LPSS_PMCSR_Dx_MASK;
|
|
|
|
|
|
|
|
mutex_lock(&lpss_iosf_mutex);
|
|
|
|
|
2018-07-26 15:58:20 +07:00
|
|
|
if (!lpss_iosf_d3_entered)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
lpss_iosf_d3_entered = false;
|
|
|
|
|
2015-12-12 08:45:06 +07: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 15:58:20 +07:00
|
|
|
exit:
|
2015-12-12 08:45:06 +07:00
|
|
|
mutex_unlock(&lpss_iosf_mutex);
|
|
|
|
}
|
|
|
|
|
2018-07-26 15:58:20 +07:00
|
|
|
static int acpi_lpss_suspend(struct device *dev, bool wakeup)
|
2014-05-23 20:15:09 +07:00
|
|
|
{
|
2014-11-05 23:34:45 +07:00
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
2014-05-23 20:15:09 +07:00
|
|
|
|
2014-11-05 23:34:45 +07:00
|
|
|
if (pdata->dev_desc->flags & LPSS_SAVE_CTX)
|
|
|
|
acpi_lpss_save_ctx(dev, pdata);
|
|
|
|
|
2017-10-16 08:29:55 +07:00
|
|
|
ret = acpi_dev_suspend(dev, wakeup);
|
2015-12-12 08:45:06 +07: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 15:58:20 +07:00
|
|
|
if (acpi_target_system_state() == ACPI_STATE_S0 &&
|
2018-06-13 18:17:26 +07:00
|
|
|
lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available())
|
2015-12-12 08:45:06 +07:00
|
|
|
lpss_iosf_enter_d3_state();
|
|
|
|
|
|
|
|
return ret;
|
2014-05-23 20:15:09 +07:00
|
|
|
}
|
|
|
|
|
2018-07-26 15:58:20 +07:00
|
|
|
static int acpi_lpss_resume(struct device *dev)
|
2014-05-23 20:15:09 +07:00
|
|
|
{
|
2014-11-05 23:34:45 +07:00
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
int ret;
|
2014-05-23 20:15:09 +07:00
|
|
|
|
2015-12-12 08:45:06 +07:00
|
|
|
/*
|
|
|
|
* This call is kept first to be in symmetry with
|
|
|
|
* acpi_lpss_runtime_suspend() one.
|
|
|
|
*/
|
2018-07-26 15:58:20 +07:00
|
|
|
if (lpss_quirks & LPSS_QUIRK_ALWAYS_POWER_ON && iosf_mbi_available())
|
2015-12-12 08:45:06 +07:00
|
|
|
lpss_iosf_exit_d3_state();
|
|
|
|
|
2017-10-10 23:49:22 +07:00
|
|
|
ret = acpi_dev_resume(dev);
|
2014-05-23 20:15:09 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-12-05 04:49:21 +07:00
|
|
|
acpi_lpss_d3_to_d0_delay(pdata);
|
|
|
|
|
2014-11-05 23:34:45 +07:00
|
|
|
if (pdata->dev_desc->flags & LPSS_SAVE_CTX)
|
|
|
|
acpi_lpss_restore_ctx(dev, pdata);
|
|
|
|
|
2017-10-16 08:29:55 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2018-09-23 20:58:11 +07:00
|
|
|
static int acpi_lpss_do_suspend_late(struct device *dev)
|
2017-10-16 08:29:55 +07: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 15:10:16 +07:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dev_pm_smart_suspend_and_suspended(dev))
|
|
|
|
return 0;
|
2017-10-16 08:29:55 +07: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 15:10:16 +07:00
|
|
|
ret = pm_generic_suspend_late(dev);
|
2018-07-26 15:58:20 +07:00
|
|
|
return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev));
|
2017-10-16 08:29:55 +07:00
|
|
|
}
|
|
|
|
|
2018-09-23 20:58:11 +07: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) {
|
|
|
|
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 08:29:55 +07:00
|
|
|
{
|
2018-07-26 15:58:20 +07:00
|
|
|
int ret = acpi_lpss_resume(dev);
|
2017-10-16 08:29:55 +07:00
|
|
|
|
|
|
|
return ret ? ret : pm_generic_resume_early(dev);
|
|
|
|
}
|
2018-09-23 20:58:11 +07: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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
ret = acpi_subsys_resume_noirq(dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (!dev_pm_may_skip_resume(dev) && pdata->dev_desc->resume_from_noirq)
|
|
|
|
ret = acpi_lpss_do_resume_early(dev);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-10-16 08:29:55 +07: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 15:58:20 +07:00
|
|
|
int ret = acpi_lpss_resume(dev);
|
2017-10-16 08:29:55 +07:00
|
|
|
|
|
|
|
return ret ? ret : pm_generic_runtime_resume(dev);
|
2014-05-23 20:15:09 +07:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
static struct dev_pm_domain acpi_lpss_pm_domain = {
|
2015-12-05 04:49:20 +07:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
.activate = acpi_lpss_activate,
|
|
|
|
.dismiss = acpi_lpss_dismiss,
|
|
|
|
#endif
|
2014-05-23 20:15:09 +07:00
|
|
|
.ops = {
|
2014-11-28 04:38:23 +07:00
|
|
|
#ifdef CONFIG_PM
|
2014-05-23 20:15:09 +07:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
.prepare = acpi_subsys_prepare,
|
2017-10-03 14:11:06 +07:00
|
|
|
.complete = acpi_subsys_complete,
|
2014-05-23 20:15:09 +07:00
|
|
|
.suspend = acpi_subsys_suspend,
|
2014-09-09 21:30:06 +07:00
|
|
|
.suspend_late = acpi_lpss_suspend_late,
|
2018-09-23 20:58:11 +07:00
|
|
|
.suspend_noirq = acpi_lpss_suspend_noirq,
|
|
|
|
.resume_noirq = acpi_lpss_resume_noirq,
|
2014-09-09 21:30:06 +07:00
|
|
|
.resume_early = acpi_lpss_resume_early,
|
2014-05-23 20:15:09 +07:00
|
|
|
.freeze = acpi_subsys_freeze,
|
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 15:10:16 +07:00
|
|
|
.freeze_late = acpi_subsys_freeze_late,
|
|
|
|
.freeze_noirq = acpi_subsys_freeze_noirq,
|
|
|
|
.thaw_noirq = acpi_subsys_thaw_noirq,
|
2014-05-23 20:15:09 +07:00
|
|
|
.poweroff = acpi_subsys_suspend,
|
2014-09-09 21:30:06 +07:00
|
|
|
.poweroff_late = acpi_lpss_suspend_late,
|
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 15:10:16 +07:00
|
|
|
.poweroff_noirq = acpi_subsys_suspend_noirq,
|
|
|
|
.restore_noirq = acpi_subsys_resume_noirq,
|
2014-09-09 21:30:06 +07:00
|
|
|
.restore_early = acpi_lpss_resume_early,
|
2014-05-23 20:15:09 +07:00
|
|
|
#endif
|
|
|
|
.runtime_suspend = acpi_lpss_runtime_suspend,
|
|
|
|
.runtime_resume = acpi_lpss_runtime_resume,
|
|
|
|
#endif
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2013-03-07 05:46:28 +07: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;
|
|
|
|
|
|
|
|
if (acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdata = acpi_driver_data(adev);
|
2014-11-05 23:34:45 +07:00
|
|
|
if (!pdata)
|
2013-03-07 05:46:28 +07:00
|
|
|
return 0;
|
|
|
|
|
2014-11-05 23:34:45 +07:00
|
|
|
if (pdata->mmio_base &&
|
|
|
|
pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) {
|
2013-03-07 05:46:28 +07:00
|
|
|
dev_err(&pdev->dev, "MMIO size insufficient to access LTR\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-23 20:15:09 +07:00
|
|
|
switch (action) {
|
2015-12-05 04:49:19 +07:00
|
|
|
case BUS_NOTIFY_BIND_DRIVER:
|
2016-01-07 22:46:13 +07:00
|
|
|
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
|
2015-12-05 04:49:18 +07:00
|
|
|
break;
|
2015-12-05 04:49:19 +07:00
|
|
|
case BUS_NOTIFY_DRIVER_NOT_BOUND:
|
2015-12-05 04:49:18 +07:00
|
|
|
case BUS_NOTIFY_UNBOUND_DRIVER:
|
2016-02-01 21:17:38 +07:00
|
|
|
dev_pm_domain_set(&pdev->dev, NULL);
|
2015-12-05 04:49:18 +07:00
|
|
|
break;
|
|
|
|
case BUS_NOTIFY_ADD_DEVICE:
|
2016-01-07 22:46:13 +07:00
|
|
|
dev_pm_domain_set(&pdev->dev, &acpi_lpss_pm_domain);
|
2014-09-02 14:55:07 +07:00
|
|
|
if (pdata->dev_desc->flags & LPSS_LTR)
|
2014-05-23 20:15:09 +07:00
|
|
|
return sysfs_create_group(&pdev->dev.kobj,
|
|
|
|
&lpss_attr_group);
|
2014-11-05 23:34:46 +07:00
|
|
|
break;
|
2014-05-23 20:15:09 +07:00
|
|
|
case BUS_NOTIFY_DEL_DEVICE:
|
2014-09-02 14:55:07 +07:00
|
|
|
if (pdata->dev_desc->flags & LPSS_LTR)
|
2014-05-23 20:15:09 +07:00
|
|
|
sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
|
2016-01-07 22:46:13 +07:00
|
|
|
dev_pm_domain_set(&pdev->dev, NULL);
|
2014-11-05 23:34:46 +07:00
|
|
|
break;
|
2014-05-23 20:15:09 +07:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-03-07 05:46:28 +07:00
|
|
|
|
2014-05-23 20:15:09 +07:00
|
|
|
return 0;
|
2013-03-07 05:46:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block acpi_lpss_nb = {
|
|
|
|
.notifier_call = acpi_lpss_platform_notify,
|
|
|
|
};
|
|
|
|
|
2014-02-11 06:35:53 +07:00
|
|
|
static void acpi_lpss_bind(struct device *dev)
|
|
|
|
{
|
|
|
|
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
|
|
2014-09-02 14:55:07 +07:00
|
|
|
if (!pdata || !pdata->mmio_base || !(pdata->dev_desc->flags & LPSS_LTR))
|
2014-02-11 06:35:53 +07: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 05:46:20 +07:00
|
|
|
static struct acpi_scan_handler lpss_handler = {
|
|
|
|
.ids = acpi_lpss_device_ids,
|
|
|
|
.attach = acpi_lpss_create_device,
|
2014-02-11 06:35:53 +07:00
|
|
|
.bind = acpi_lpss_bind,
|
|
|
|
.unbind = acpi_lpss_unbind,
|
2013-03-07 05:46:20 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
void __init acpi_lpss_init(void)
|
|
|
|
{
|
2015-12-12 08:45:06 +07:00
|
|
|
const struct x86_cpu_id *id;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = lpt_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);
|
2013-03-07 05:46:20 +07:00
|
|
|
}
|
2014-05-30 19:34:05 +07: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 */
|