summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/intel
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/intel')
-rw-r--r--drivers/platform/x86/intel/Kconfig149
-rw-r--r--drivers/platform/x86/intel/Makefile39
-rw-r--r--drivers/platform/x86/intel/atomisp2/Kconfig43
-rw-r--r--drivers/platform/x86/intel/atomisp2/Makefile9
-rw-r--r--drivers/platform/x86/intel/atomisp2/led.c116
-rw-r--r--drivers/platform/x86/intel/atomisp2/pm.c143
-rw-r--r--drivers/platform/x86/intel/bxtwc_tmu.c147
-rw-r--r--drivers/platform/x86/intel/chtdc_ti_pwrbtn.c94
-rw-r--r--drivers/platform/x86/intel/hid.c732
-rw-r--r--drivers/platform/x86/intel/int0002_vgpio.c294
-rw-r--r--drivers/platform/x86/intel/int1092/Kconfig14
-rw-r--r--drivers/platform/x86/intel/int1092/Makefile1
-rw-r--r--drivers/platform/x86/intel/int1092/intel_sar.c316
-rw-r--r--drivers/platform/x86/intel/int1092/intel_sar.h86
-rw-r--r--drivers/platform/x86/intel/int33fe/Makefile2
-rw-r--r--drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c29
-rw-r--r--drivers/platform/x86/intel/int3472/Makefile2
-rw-r--r--drivers/platform/x86/intel/mrfld_pwrbtn.c107
-rw-r--r--drivers/platform/x86/intel/oaktrail.c377
-rw-r--r--drivers/platform/x86/intel/pmc/Kconfig25
-rw-r--r--drivers/platform/x86/intel/pmc/Makefile9
-rw-r--r--drivers/platform/x86/intel/pmc/core.c2160
-rw-r--r--drivers/platform/x86/intel/pmc/core.h363
-rw-r--r--drivers/platform/x86/intel/pmc/pltdrv.c80
-rw-r--r--drivers/platform/x86/intel/pmt/Kconfig40
-rw-r--r--drivers/platform/x86/intel/pmt/Makefile12
-rw-r--r--drivers/platform/x86/intel/pmt/class.c344
-rw-r--r--drivers/platform/x86/intel/pmt/class.h53
-rw-r--r--drivers/platform/x86/intel/pmt/crashlog.c327
-rw-r--r--drivers/platform/x86/intel/pmt/telemetry.c148
-rw-r--r--drivers/platform/x86/intel/punit_ipc.c342
-rw-r--r--drivers/platform/x86/intel/rst.c142
-rw-r--r--drivers/platform/x86/intel/smartconnect.c44
-rw-r--r--drivers/platform/x86/intel/speed_select_if/Kconfig17
-rw-r--r--drivers/platform/x86/intel/speed_select_if/Makefile10
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.c741
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.h72
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c214
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c227
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c200
-rw-r--r--drivers/platform/x86/intel/telemetry/Kconfig16
-rw-r--r--drivers/platform/x86/intel/telemetry/Makefile11
-rw-r--r--drivers/platform/x86/intel/telemetry/core.c450
-rw-r--r--drivers/platform/x86/intel/telemetry/debugfs.c961
-rw-r--r--drivers/platform/x86/intel/telemetry/pltdrv.c1189
-rw-r--r--drivers/platform/x86/intel/turbo_max_3.c139
-rw-r--r--drivers/platform/x86/intel/uncore-frequency.c451
-rw-r--r--drivers/platform/x86/intel/vbtn.c414
-rw-r--r--drivers/platform/x86/intel/wmi/Kconfig31
-rw-r--r--drivers/platform/x86/intel/wmi/Makefile9
-rw-r--r--drivers/platform/x86/intel/wmi/sbl-fw-update.c144
-rw-r--r--drivers/platform/x86/intel/wmi/thunderbolt.c94
52 files changed, 12149 insertions, 30 deletions
diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig
index f2eef337eb98..0b21468e1bd0 100644
--- a/drivers/platform/x86/intel/Kconfig
+++ b/drivers/platform/x86/intel/Kconfig
@@ -16,7 +16,156 @@ menuconfig X86_PLATFORM_DRIVERS_INTEL
if X86_PLATFORM_DRIVERS_INTEL
+source "drivers/platform/x86/intel/atomisp2/Kconfig"
+source "drivers/platform/x86/intel/int1092/Kconfig"
source "drivers/platform/x86/intel/int33fe/Kconfig"
source "drivers/platform/x86/intel/int3472/Kconfig"
+source "drivers/platform/x86/intel/pmc/Kconfig"
+source "drivers/platform/x86/intel/pmt/Kconfig"
+source "drivers/platform/x86/intel/speed_select_if/Kconfig"
+source "drivers/platform/x86/intel/telemetry/Kconfig"
+source "drivers/platform/x86/intel/wmi/Kconfig"
+
+config INTEL_HID_EVENT
+ tristate "Intel HID Event"
+ depends on ACPI
+ depends on INPUT
+ depends on I2C
+ select INPUT_SPARSEKMAP
+ help
+ This driver provides support for the Intel HID Event hotkey interface.
+ Some laptops require this driver for hotkey support.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel_hid.
+
+config INTEL_VBTN
+ tristate "Intel Virtual Button"
+ depends on ACPI
+ depends on INPUT
+ depends on I2C
+ select INPUT_SPARSEKMAP
+ help
+ This driver provides support for the Intel Virtual Button interface.
+ Some laptops require this driver for power button support.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel_vbtn.
+
+config INTEL_INT0002_VGPIO
+ tristate "Intel ACPI INT0002 Virtual GPIO driver"
+ depends on GPIOLIB && ACPI && PM_SLEEP
+ select GPIOLIB_IRQCHIP
+ help
+ Some peripherals on Bay Trail and Cherry Trail platforms signal a
+ Power Management Event (PME) to the Power Management Controller (PMC)
+ to wakeup the system. When this happens software needs to explicitly
+ clear the PME bus 0 status bit in the GPE0a_STS register to avoid an
+ IRQ storm on IRQ 9.
+
+ This is modelled in ACPI through the INT0002 ACPI device, which is
+ called a "Virtual GPIO controller" in ACPI because it defines the
+ event handler to call when the PME triggers through _AEI and _L02
+ methods as would be done for a real GPIO interrupt in ACPI.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel_int0002_vgpio.
+
+config INTEL_OAKTRAIL
+ tristate "Intel Oaktrail Platform Extras"
+ depends on ACPI
+ depends on ACPI_VIDEO || ACPI_VIDEO=n
+ depends on RFKILL && BACKLIGHT_CLASS_DEVICE && ACPI
+ help
+ Intel Oaktrail platform need this driver to provide interfaces to
+ enable/disable the Camera, WiFi, BT etc. devices. If in doubt, say Y
+ here; it will only load on supported platforms.
+
+config INTEL_BXTWC_PMIC_TMU
+ tristate "Intel Broxton Whiskey Cove TMU Driver"
+ depends on INTEL_SOC_PMIC_BXTWC
+ depends on MFD_INTEL_PMC_BXT
+ select REGMAP
+ help
+ Select this driver to use Intel Broxton Whiskey Cove PMIC TMU feature.
+ This driver enables the alarm wakeup functionality in the TMU unit of
+ Whiskey Cove PMIC.
+
+config INTEL_CHTDC_TI_PWRBTN
+ tristate "Intel Cherry Trail Dollar Cove TI power button driver"
+ depends on INTEL_SOC_PMIC_CHTDC_TI
+ depends on INPUT
+ help
+ This option adds a power button driver for Dollar Cove TI
+ PMIC on Intel Cherry Trail devices.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_chtdc_ti_pwrbtn.
+
+config INTEL_MRFLD_PWRBTN
+ tristate "Intel Merrifield Basin Cove power button driver"
+ depends on INTEL_SOC_PMIC_MRFLD
+ depends on INPUT
+ help
+ This option adds a power button driver for Basin Cove PMIC
+ on Intel Merrifield devices.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_mrfld_pwrbtn.
+
+config INTEL_PUNIT_IPC
+ tristate "Intel P-Unit IPC Driver"
+ help
+ This driver provides support for Intel P-Unit Mailbox IPC mechanism,
+ which is used to bridge the communications between kernel and P-Unit.
+
+config INTEL_RST
+ tristate "Intel Rapid Start Technology Driver"
+ depends on ACPI
+ help
+ This driver provides support for modifying parameters on systems
+ equipped with Intel's Rapid Start Technology. When put in an ACPI
+ sleep state, these devices will wake after either a configured
+ timeout or when the system battery reaches a critical state,
+ automatically copying memory contents to disk. On resume, the
+ firmware will copy the memory contents back to RAM and resume the OS
+ as usual.
+
+config INTEL_SMARTCONNECT
+ tristate "Intel Smart Connect disabling driver"
+ depends on ACPI
+ help
+ Intel Smart Connect is a technology intended to permit devices to
+ update state by resuming for a short period of time at regular
+ intervals. If a user enables this functionality under Windows and
+ then reboots into Linux, the system may remain configured to resume
+ on suspend. In the absence of any userspace to support it, the system
+ will then remain awake until something triggers another suspend.
+
+ This driver checks to determine whether the device has Intel Smart
+ Connect enabled, and if so disables it.
+
+config INTEL_TURBO_MAX_3
+ bool "Intel Turbo Boost Max Technology 3.0 enumeration driver"
+ depends on X86_64 && SCHED_MC_PRIO
+ help
+ This driver reads maximum performance ratio of each CPU and set up
+ the scheduler priority metrics. In this way scheduler can prefer
+ CPU with higher performance to schedule tasks.
+
+ This driver is only required when the system is not using Hardware
+ P-States (HWP). In HWP mode, priority can be read from ACPI tables.
+
+config INTEL_UNCORE_FREQ_CONTROL
+ tristate "Intel Uncore frequency control driver"
+ depends on X86_64
+ help
+ This driver allows control of Uncore frequency limits on
+ supported server platforms.
+
+ Uncore frequency controls RING/LLC (last-level cache) clocks.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel-uncore-frequency.
endif # X86_PLATFORM_DRIVERS_INTEL
diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile
index 0653055942d5..8b3a3f7bab49 100644
--- a/drivers/platform/x86/intel/Makefile
+++ b/drivers/platform/x86/intel/Makefile
@@ -4,5 +4,44 @@
# Intel x86 Platform-Specific Drivers
#
+obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/
+obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/
obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/
obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/
+obj-$(CONFIG_INTEL_PMC_CORE) += pmc/
+obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/
+obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/
+obj-$(CONFIG_INTEL_WMI) += wmi/
+
+# Intel input drivers
+intel-hid-y := hid.o
+obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
+intel-vbtn-y := vbtn.o
+obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o
+
+# Intel miscellaneous drivers
+intel_int0002_vgpio-y := int0002_vgpio.o
+obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
+intel_oaktrail-y := oaktrail.o
+obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
+
+# Intel PMIC / PMC / P-Unit drivers
+intel_bxtwc_tmu-y := bxtwc_tmu.o
+obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
+intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o
+obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o
+intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o
+obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
+intel_punit_ipc-y := punit_ipc.o
+obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
+
+# Intel Uncore drivers
+intel-rst-y := rst.o
+obj-$(CONFIG_INTEL_RST) += intel-rst.o
+intel-smartconnect-y := smartconnect.o
+obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
+intel_turbo_max_3-y := turbo_max_3.o
+obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
+intel-uncore-frequency-y := uncore-frequency.o
+obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
diff --git a/drivers/platform/x86/intel/atomisp2/Kconfig b/drivers/platform/x86/intel/atomisp2/Kconfig
new file mode 100644
index 000000000000..35dd2be9d2a1
--- /dev/null
+++ b/drivers/platform/x86/intel/atomisp2/Kconfig
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+config INTEL_ATOMISP2_PDX86
+ bool
+
+config INTEL_ATOMISP2_LED
+ tristate "Intel AtomISP v2 camera LED driver"
+ depends on GPIOLIB && LEDS_GPIO
+ select INTEL_ATOMISP2_PDX86
+ help
+ Many Bay Trail and Cherry Trail devices come with a camera attached
+ to Intel's Image Signal Processor. Linux currently does not have a
+ driver for these, so they do not work as a camera. Some of these
+ camera's have a LED which is controlled through a GPIO.
+
+ Some of these devices have a firmware issue where the LED gets turned
+ on at boot. This driver will turn the LED off at boot and also allows
+ controlling the LED (repurposing it) through the sysfs LED interface.
+
+ Which GPIO is attached to the LED is usually not described in the
+ ACPI tables, so this driver contains per-system info about the GPIO
+ inside the driver, this means that this driver only works on systems
+ the driver knows about.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_atomisp2_led.
+
+config INTEL_ATOMISP2_PM
+ tristate "Intel AtomISP v2 dummy / power-management driver"
+ depends on PCI && IOSF_MBI && PM
+ depends on !INTEL_ATOMISP
+ select INTEL_ATOMISP2_PDX86
+ help
+ Power-management driver for Intel's Image Signal Processor found on
+ Bay Trail and Cherry Trail devices. This dummy driver's sole purpose
+ is to turn the ISP off (put it in D3) to save power and to allow
+ entering of S0ix modes.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_atomisp2_pm.
diff --git a/drivers/platform/x86/intel/atomisp2/Makefile b/drivers/platform/x86/intel/atomisp2/Makefile
new file mode 100644
index 000000000000..96b1e877d1f1
--- /dev/null
+++ b/drivers/platform/x86/intel/atomisp2/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+intel_atomisp2_led-y := led.o
+obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o
+intel_atomisp2_pm-y += pm.o
+obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o
diff --git a/drivers/platform/x86/intel/atomisp2/led.c b/drivers/platform/x86/intel/atomisp2/led.c
new file mode 100644
index 000000000000..5935dfca166f
--- /dev/null
+++ b/drivers/platform/x86/intel/atomisp2/led.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for controlling LEDs for cameras connected to the Intel atomisp2
+ * The main purpose of this driver is to turn off LEDs which are on at boot.
+ *
+ * Copyright (C) 2020 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/dmi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+/* This must be leds-gpio as the leds-gpio driver binds to the name */
+#define DEV_NAME "leds-gpio"
+
+static const struct gpio_led atomisp2_leds[] = {
+ {
+ .name = "atomisp2::camera",
+ .default_state = LEDS_GPIO_DEFSTATE_OFF,
+ },
+};
+
+static const struct gpio_led_platform_data atomisp2_leds_pdata = {
+ .num_leds = ARRAY_SIZE(atomisp2_leds),
+ .leds = atomisp2_leds,
+};
+
+static struct gpiod_lookup_table asus_t100ta_lookup = {
+ .dev_id = DEV_NAME,
+ .table = {
+ GPIO_LOOKUP_IDX("INT33FC:02", 8, NULL, 0, GPIO_ACTIVE_HIGH),
+ { }
+ }
+};
+
+static struct gpiod_lookup_table asus_t100chi_lookup = {
+ .dev_id = DEV_NAME,
+ .table = {
+ GPIO_LOOKUP_IDX("INT33FC:01", 24, NULL, 0, GPIO_ACTIVE_HIGH),
+ { }
+ }
+};
+
+static const struct dmi_system_id atomisp2_led_systems[] __initconst = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TA"),
+ },
+ .driver_data = &asus_t100ta_lookup,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T200TA"),
+ },
+ .driver_data = &asus_t100ta_lookup,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100CHI"),
+ },
+ .driver_data = &asus_t100chi_lookup,
+ },
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(dmi, atomisp2_led_systems);
+
+static struct gpiod_lookup_table *gpio_lookup;
+static struct platform_device *pdev;
+
+static int __init atomisp2_led_init(void)
+{
+ const struct dmi_system_id *system;
+
+ system = dmi_first_match(atomisp2_led_systems);
+ if (!system)
+ return -ENODEV;
+
+ gpio_lookup = system->driver_data;
+ gpiod_add_lookup_table(gpio_lookup);
+
+ pdev = platform_device_register_resndata(NULL,
+ DEV_NAME, PLATFORM_DEVID_NONE,
+ NULL, 0, &atomisp2_leds_pdata,
+ sizeof(atomisp2_leds_pdata));
+ if (IS_ERR(pdev))
+ gpiod_remove_lookup_table(gpio_lookup);
+
+ return PTR_ERR_OR_ZERO(pdev);
+}
+
+static void __exit atomisp2_led_cleanup(void)
+{
+ platform_device_unregister(pdev);
+ gpiod_remove_lookup_table(gpio_lookup);
+}
+
+module_init(atomisp2_led_init);
+module_exit(atomisp2_led_cleanup);
+
+/*
+ * The ACPI INIT method from Asus WMI's code on the T100TA and T200TA turns the
+ * LED on (without the WMI interface allowing further control over the LED).
+ * Ensure we are loaded after asus-nb-wmi so that we turn the LED off again.
+ */
+MODULE_SOFTDEP("pre: asus_nb_wmi");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com");
+MODULE_DESCRIPTION("Intel atomisp2 camera LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/atomisp2/pm.c b/drivers/platform/x86/intel/atomisp2/pm.c
new file mode 100644
index 000000000000..805fc0d8515c
--- /dev/null
+++ b/drivers/platform/x86/intel/atomisp2/pm.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Dummy driver for Intel's Image Signal Processor found on Bay Trail
+ * and Cherry Trail devices. The sole purpose of this driver is to allow
+ * the ISP to be put in D3.
+ *
+ * Copyright (C) 2018 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on various non upstream patches for ISP support:
+ * Copyright (C) 2010-2017 Intel Corporation. All rights reserved.
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.com.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <asm/iosf_mbi.h>
+
+/* PCI configuration regs */
+#define PCI_INTERRUPT_CTRL 0x9c
+
+#define PCI_CSI_CONTROL 0xe8
+#define PCI_CSI_CONTROL_PORTS_OFF_MASK 0x7
+
+/* IOSF BT_MBI_UNIT_PMC regs */
+#define ISPSSPM0 0x39
+#define ISPSSPM0_ISPSSC_OFFSET 0
+#define ISPSSPM0_ISPSSC_MASK 0x00000003
+#define ISPSSPM0_ISPSSS_OFFSET 24
+#define ISPSSPM0_ISPSSS_MASK 0x03000000
+#define ISPSSPM0_IUNIT_POWER_ON 0x0
+#define ISPSSPM0_IUNIT_POWER_OFF 0x3
+
+static int isp_set_power(struct pci_dev *dev, bool enable)
+{
+ unsigned long timeout;
+ u32 val = enable ? ISPSSPM0_IUNIT_POWER_ON : ISPSSPM0_IUNIT_POWER_OFF;
+
+ /* Write to ISPSSPM0 bit[1:0] to power on/off the IUNIT */
+ iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0,
+ val, ISPSSPM0_ISPSSC_MASK);
+
+ /*
+ * There should be no IUNIT access while power-down is
+ * in progress. HW sighting: 4567865.
+ * Wait up to 50 ms for the IUNIT to shut down.
+ * And we do the same for power on.
+ */
+ timeout = jiffies + msecs_to_jiffies(50);
+ do {
+ u32 tmp;
+
+ /* Wait until ISPSSPM0 bit[25:24] shows the right value */
+ iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, &tmp);
+ tmp = (tmp & ISPSSPM0_ISPSSS_MASK) >> ISPSSPM0_ISPSSS_OFFSET;
+ if (tmp == val)
+ return 0;
+
+ usleep_range(1000, 2000);
+ } while (time_before(jiffies, timeout));
+
+ dev_err(&dev->dev, "IUNIT power-%s timeout.\n", enable ? "on" : "off");
+ return -EBUSY;
+}
+
+static int isp_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ pm_runtime_allow(&dev->dev);
+ pm_runtime_put_sync_suspend(&dev->dev);
+
+ return 0;
+}
+
+static void isp_remove(struct pci_dev *dev)
+{
+ pm_runtime_get_sync(&dev->dev);
+ pm_runtime_forbid(&dev->dev);
+}
+
+static int isp_pci_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u32 val;
+
+ pci_write_config_dword(pdev, PCI_INTERRUPT_CTRL, 0);
+
+ /*
+ * MRFLD IUNIT DPHY is located in an always-power-on island
+ * MRFLD HW design need all CSI ports are disabled before
+ * powering down the IUNIT.
+ */
+ pci_read_config_dword(pdev, PCI_CSI_CONTROL, &val);
+ val |= PCI_CSI_CONTROL_PORTS_OFF_MASK;
+ pci_write_config_dword(pdev, PCI_CSI_CONTROL, val);
+
+ /*
+ * We lose config space access when punit power gates
+ * the ISP. Can't use pci_set_power_state() because
+ * pmcsr won't actually change when we write to it.
+ */
+ pci_save_state(pdev);
+ pdev->current_state = PCI_D3cold;
+ isp_set_power(pdev, false);
+
+ return 0;
+}
+
+static int isp_pci_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ isp_set_power(pdev, true);
+ pdev->current_state = PCI_D0;
+ pci_restore_state(pdev);
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(isp_pm_ops, isp_pci_suspend,
+ isp_pci_resume, NULL);
+
+static const struct pci_device_id isp_id_table[] = {
+ { PCI_VDEVICE(INTEL, 0x0f38), },
+ { PCI_VDEVICE(INTEL, 0x22b8), },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, isp_id_table);
+
+static struct pci_driver isp_pci_driver = {
+ .name = "intel_atomisp2_pm",
+ .id_table = isp_id_table,
+ .probe = isp_probe,
+ .remove = isp_remove,
+ .driver.pm = &isp_pm_ops,
+};
+
+module_pci_driver(isp_pci_driver);
+
+MODULE_DESCRIPTION("Intel AtomISP2 dummy / power-management drv (for suspend)");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/bxtwc_tmu.c b/drivers/platform/x86/intel/bxtwc_tmu.c
new file mode 100644
index 000000000000..7ccf583649e6
--- /dev/null
+++ b/drivers/platform/x86/intel/bxtwc_tmu.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel BXT Whiskey Cove PMIC TMU driver
+ *
+ * Copyright (C) 2016 Intel Corporation. All rights reserved.
+ *
+ * This driver adds TMU (Time Management Unit) support for Intel BXT platform.
+ * It enables the alarm wake-up functionality in the TMU unit of Whiskey Cove
+ * PMIC.
+ */
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/intel_soc_pmic.h>
+
+#define BXTWC_TMUIRQ 0x4fb6
+#define BXTWC_MIRQLVL1 0x4e0e
+#define BXTWC_MTMUIRQ_REG 0x4fb7
+#define BXTWC_MIRQLVL1_MTMU BIT(1)
+#define BXTWC_TMU_WK_ALRM BIT(1)
+#define BXTWC_TMU_SYS_ALRM BIT(2)
+#define BXTWC_TMU_ALRM_MASK (BXTWC_TMU_WK_ALRM | BXTWC_TMU_SYS_ALRM)
+#define BXTWC_TMU_ALRM_IRQ (BXTWC_TMU_WK_ALRM | BXTWC_TMU_SYS_ALRM)
+
+struct wcove_tmu {
+ int irq;
+ struct device *dev;
+ struct regmap *regmap;
+};
+
+static irqreturn_t bxt_wcove_tmu_irq_handler(int irq, void *data)
+{
+ struct wcove_tmu *wctmu = data;
+ unsigned int tmu_irq;
+
+ /* Read TMU interrupt reg */
+ regmap_read(wctmu->regmap, BXTWC_TMUIRQ, &tmu_irq);
+ if (tmu_irq & BXTWC_TMU_ALRM_IRQ) {
+ /* clear TMU irq */
+ regmap_write(wctmu->regmap, BXTWC_TMUIRQ, tmu_irq);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int bxt_wcove_tmu_probe(struct platform_device *pdev)
+{
+ struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+ struct regmap_irq_chip_data *regmap_irq_chip;
+ struct wcove_tmu *wctmu;
+ int ret, virq, irq;
+
+ wctmu = devm_kzalloc(&pdev->dev, sizeof(*wctmu), GFP_KERNEL);
+ if (!wctmu)
+ return -ENOMEM;
+
+ wctmu->dev = &pdev->dev;
+ wctmu->regmap = pmic->regmap;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ regmap_irq_chip = pmic->irq_chip_data_tmu;
+ virq = regmap_irq_get_virq(regmap_irq_chip, irq);
+ if (virq < 0) {
+ dev_err(&pdev->dev,
+ "failed to get virtual interrupt=%d\n", irq);
+ return virq;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, virq,
+ NULL, bxt_wcove_tmu_irq_handler,
+ IRQF_ONESHOT, "bxt_wcove_tmu", wctmu);
+ if (ret) {
+ dev_err(&pdev->dev, "request irq failed: %d,virq: %d\n",
+ ret, virq);
+ return ret;
+ }
+ wctmu->irq = virq;
+
+ /* Unmask TMU second level Wake & System alarm */
+ regmap_update_bits(wctmu->regmap, BXTWC_MTMUIRQ_REG,
+ BXTWC_TMU_ALRM_MASK, 0);
+
+ platform_set_drvdata(pdev, wctmu);
+ return 0;
+}
+
+static int bxt_wcove_tmu_remove(struct platform_device *pdev)
+{
+ struct wcove_tmu *wctmu = platform_get_drvdata(pdev);
+ unsigned int val;
+
+ /* Mask TMU interrupts */
+ regmap_read(wctmu->regmap, BXTWC_MIRQLVL1, &val);
+ regmap_write(wctmu->regmap, BXTWC_MIRQLVL1,
+ val | BXTWC_MIRQLVL1_MTMU);
+ regmap_read(wctmu->regmap, BXTWC_MTMUIRQ_REG, &val);
+ regmap_write(wctmu->regmap, BXTWC_MTMUIRQ_REG,
+ val | BXTWC_TMU_ALRM_MASK);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bxtwc_tmu_suspend(struct device *dev)
+{
+ struct wcove_tmu *wctmu = dev_get_drvdata(dev);
+
+ enable_irq_wake(wctmu->irq);
+ return 0;
+}
+
+static int bxtwc_tmu_resume(struct device *dev)
+{
+ struct wcove_tmu *wctmu = dev_get_drvdata(dev);
+
+ disable_irq_wake(wctmu->irq);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(bxtwc_tmu_pm_ops, bxtwc_tmu_suspend, bxtwc_tmu_resume);
+
+static const struct platform_device_id bxt_wcove_tmu_id_table[] = {
+ { .name = "bxt_wcove_tmu" },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, bxt_wcove_tmu_id_table);
+
+static struct platform_driver bxt_wcove_tmu_driver = {
+ .probe = bxt_wcove_tmu_probe,
+ .remove = bxt_wcove_tmu_remove,
+ .driver = {
+ .name = "bxt_wcove_tmu",
+ .pm = &bxtwc_tmu_pm_ops,
+ },
+ .id_table = bxt_wcove_tmu_id_table,
+};
+
+module_platform_driver(bxt_wcove_tmu_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Nilesh Bacchewar <nilesh.bacchewar@intel.com>");
+MODULE_DESCRIPTION("BXT Whiskey Cove TMU Driver");
diff --git a/drivers/platform/x86/intel/chtdc_ti_pwrbtn.c b/drivers/platform/x86/intel/chtdc_ti_pwrbtn.c
new file mode 100644
index 000000000000..9606a994af22
--- /dev/null
+++ b/drivers/platform/x86/intel/chtdc_ti_pwrbtn.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Power-button driver for Dollar Cove TI PMIC
+ * Copyright (C) 2014 Intel Corp
+ * Copyright (c) 2017 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/slab.h>
+
+#define CHTDC_TI_SIRQ_REG 0x3
+#define SIRQ_PWRBTN_REL BIT(0)
+
+static irqreturn_t chtdc_ti_pwrbtn_interrupt(int irq, void *dev_id)
+{
+ struct input_dev *input = dev_id;
+ struct device *dev = input->dev.parent;
+ struct regmap *regmap = dev_get_drvdata(dev);
+ int state;
+
+ if (!regmap_read(regmap, CHTDC_TI_SIRQ_REG, &state)) {
+ dev_dbg(dev, "SIRQ_REG=0x%x\n", state);
+ input_report_key(input, KEY_POWER, !(state & SIRQ_PWRBTN_REL));
+ input_sync(input);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int chtdc_ti_pwrbtn_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct input_dev *input;
+ int irq, err;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+ input->name = pdev->name;
+ input->phys = "power-button/input0";
+ input->id.bustype = BUS_HOST;
+ input_set_capability(input, EV_KEY, KEY_POWER);
+ err = input_register_device(input);
+ if (err)
+ return err;
+
+ dev_set_drvdata(dev, pmic->regmap);
+
+ err = devm_request_threaded_irq(dev, irq, NULL,
+ chtdc_ti_pwrbtn_interrupt,
+ IRQF_ONESHOT, KBUILD_MODNAME, input);
+ if (err)
+ return err;
+
+ device_init_wakeup(dev, true);
+ dev_pm_set_wake_irq(dev, irq);
+ return 0;
+}
+
+static int chtdc_ti_pwrbtn_remove(struct platform_device *pdev)
+{
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+ return 0;
+}
+
+static const struct platform_device_id chtdc_ti_pwrbtn_id_table[] = {
+ { .name = "chtdc_ti_pwrbtn" },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, chtdc_ti_pwrbtn_id_table);
+
+static struct platform_driver chtdc_ti_pwrbtn_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+ .probe = chtdc_ti_pwrbtn_probe,
+ .remove = chtdc_ti_pwrbtn_remove,
+ .id_table = chtdc_ti_pwrbtn_id_table,
+};
+module_platform_driver(chtdc_ti_pwrbtn_driver);
+
+MODULE_DESCRIPTION("Power-button driver for Dollar Cove TI PMIC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c
new file mode 100644
index 000000000000..a33a5826e81a
--- /dev/null
+++ b/drivers/platform/x86/intel/hid.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel HID event & 5 button array driver
+ *
+ * Copyright (C) 2015 Alex Hung <alex.hung@canonical.com>
+ * Copyright (C) 2015 Andrew Lutomirski <luto@kernel.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include "../dual_accel_detect.h"
+
+/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
+#define TABLET_MODE_FLAG BIT(6)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alex Hung");
+
+static const struct acpi_device_id intel_hid_ids[] = {
+ {"INT33D5", 0},
+ {"INTC1051", 0},
+ {"INTC1054", 0},
+ {"INTC1070", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
+
+/* In theory, these are HID usages. */
+static const struct key_entry intel_hid_keymap[] = {
+ /* 1: LSuper (Page 0x07, usage 0xE3) -- unclear what to do */
+ /* 2: Toggle SW_ROTATE_LOCK -- easy to implement if seen in wild */
+ { KE_KEY, 3, { KEY_NUMLOCK } },
+ { KE_KEY, 4, { KEY_HOME } },
+ { KE_KEY, 5, { KEY_END } },
+ { KE_KEY, 6, { KEY_PAGEUP } },
+ { KE_KEY, 7, { KEY_PAGEDOWN } },
+ { KE_KEY, 8, { KEY_RFKILL } },
+ { KE_KEY, 9, { KEY_POWER } },
+ { KE_KEY, 11, { KEY_SLEEP } },
+ /* 13 has two different meanings in the spec -- ignore it. */
+ { KE_KEY, 14, { KEY_STOPCD } },
+ { KE_KEY, 15, { KEY_PLAYPAUSE } },
+ { KE_KEY, 16, { KEY_MUTE } },
+ { KE_KEY, 17, { KEY_VOLUMEUP } },
+ { KE_KEY, 18, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 19, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 20, { KEY_BRIGHTNESSDOWN } },
+ /* 27: wake -- needs special handling */
+ { KE_END },
+};
+
+/* 5 button array notification value. */
+static const struct key_entry intel_array_keymap[] = {
+ { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* Press */
+ { KE_IGNORE, 0xC3, { KEY_LEFTMETA } }, /* Release */
+ { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* Press */
+ { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* Release */
+ { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* Press */
+ { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* Release */
+ { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* Press */
+ { KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* Release */
+ { KE_KEY, 0xCE, { KEY_POWER } }, /* Press */
+ { KE_IGNORE, 0xCF, { KEY_POWER } }, /* Release */
+ { KE_END },
+};
+
+static const struct dmi_system_id button_array_table[] = {
+ {
+ .ident = "Wacom MobileStudio Pro 13",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"),
+ },
+ },
+ {
+ .ident = "Wacom MobileStudio Pro 16",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"),
+ },
+ },
+ {
+ .ident = "HP Spectre x2 (2015)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"),
+ },
+ },
+ {
+ .ident = "Lenovo ThinkPad X1 Tablet Gen 2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"),
+ },
+ },
+ { }
+};
+
+/*
+ * Some convertible use the intel-hid ACPI interface to report SW_TABLET_MODE,
+ * these need to be compared via a DMI based authorization list because some
+ * models have unreliable VGBS return which could cause incorrect
+ * SW_TABLET_MODE report.
+ */
+static const struct dmi_system_id dmi_vgbs_allow_list[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"),
+ },
+ },
+ { }
+};
+
+struct intel_hid_priv {
+ struct input_dev *input_dev;
+ struct input_dev *array;
+ struct input_dev *switches;
+ bool wakeup_mode;
+ bool dual_accel;
+};
+
+#define HID_EVENT_FILTER_UUID "eeec56b3-4442-408f-a792-4edd4d758054"
+
+enum intel_hid_dsm_fn_codes {
+ INTEL_HID_DSM_FN_INVALID,
+ INTEL_HID_DSM_BTNL_FN,
+ INTEL_HID_DSM_HDMM_FN,
+ INTEL_HID_DSM_HDSM_FN,
+ INTEL_HID_DSM_HDEM_FN,
+ INTEL_HID_DSM_BTNS_FN,
+ INTEL_HID_DSM_BTNE_FN,
+ INTEL_HID_DSM_HEBC_V1_FN,
+ INTEL_HID_DSM_VGBS_FN,
+ INTEL_HID_DSM_HEBC_V2_FN,
+ INTEL_HID_DSM_FN_MAX
+};
+
+static const char *intel_hid_dsm_fn_to_method[INTEL_HID_DSM_FN_MAX] = {
+ NULL,
+ "BTNL",
+ "HDMM",
+ "HDSM",
+ "HDEM",
+ "BTNS",
+ "BTNE",
+ "HEBC",
+ "VGBS",
+ "HEBC"
+};
+
+static unsigned long long intel_hid_dsm_fn_mask;
+static guid_t intel_dsm_guid;
+
+static bool intel_hid_execute_method(acpi_handle handle,
+ enum intel_hid_dsm_fn_codes fn_index,
+ unsigned long long arg)
+{
+ union acpi_object *obj, argv4, req;
+ acpi_status status;
+ char *method_name;
+
+ if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
+ fn_index >= INTEL_HID_DSM_FN_MAX)
+ return false;
+
+ method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
+
+ if (!(intel_hid_dsm_fn_mask & BIT(fn_index)))
+ goto skip_dsm_exec;
+
+ /* All methods expects a package with one integer element */
+ req.type = ACPI_TYPE_INTEGER;
+ req.integer.value = arg;
+
+ argv4.type = ACPI_TYPE_PACKAGE;
+ argv4.package.count = 1;
+ argv4.package.elements = &req;
+
+ obj = acpi_evaluate_dsm(handle, &intel_dsm_guid, 1, fn_index, &argv4);
+ if (obj) {
+ acpi_handle_debug(handle, "Exec DSM Fn code: %d[%s] success\n",
+ fn_index, method_name);
+ ACPI_FREE(obj);
+ return true;
+ }
+
+skip_dsm_exec:
+ status = acpi_execute_simple_method(handle, method_name, arg);
+ if (ACPI_SUCCESS(status))
+ return true;
+
+ return false;
+}
+
+static bool intel_hid_evaluate_method(acpi_handle handle,
+ enum intel_hid_dsm_fn_codes fn_index,
+ unsigned long long *result)
+{
+ union acpi_object *obj;
+ acpi_status status;
+ char *method_name;
+
+ if (fn_index <= INTEL_HID_DSM_FN_INVALID ||
+ fn_index >= INTEL_HID_DSM_FN_MAX)
+ return false;
+
+ method_name = (char *)intel_hid_dsm_fn_to_method[fn_index];
+
+ if (!(intel_hid_dsm_fn_mask & fn_index))
+ goto skip_dsm_eval;
+
+ obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid,
+ 1, fn_index,
+ NULL, ACPI_TYPE_INTEGER);
+ if (obj) {
+ *result = obj->integer.value;
+ acpi_handle_debug(handle,
+ "Eval DSM Fn code: %d[%s] results: 0x%llx\n",
+ fn_index, method_name, *result);
+ ACPI_FREE(obj);
+ return true;
+ }
+
+skip_dsm_eval:
+ status = acpi_evaluate_integer(handle, method_name, NULL, result);
+ if (ACPI_SUCCESS(status))
+ return true;
+
+ return false;
+}
+
+static void intel_hid_init_dsm(acpi_handle handle)
+{
+ union acpi_object *obj;
+
+ guid_parse(HID_EVENT_FILTER_UUID, &intel_dsm_guid);
+
+ obj = acpi_evaluate_dsm_typed(handle, &intel_dsm_guid, 1, 0, NULL,
+ ACPI_TYPE_BUFFER);
+ if (obj) {
+ switch (obj->buffer.length) {
+ default:
+ case 2:
+ intel_hid_dsm_fn_mask = *(u16 *)obj->buffer.pointer;
+ break;
+ case 1:
+ intel_hid_dsm_fn_mask = *obj->buffer.pointer;
+ break;
+ case 0:
+ acpi_handle_warn(handle, "intel_hid_dsm_fn_mask length is zero\n");
+ intel_hid_dsm_fn_mask = 0;
+ break;
+ }
+ ACPI_FREE(obj);
+ }
+
+ acpi_handle_debug(handle, "intel_hid_dsm_fn_mask = %llx\n",
+ intel_hid_dsm_fn_mask);
+}
+
+static int intel_hid_set_enable(struct device *device, bool enable)
+{
+ acpi_handle handle = ACPI_HANDLE(device);
+
+ /* Enable|disable features - power button is always enabled */
+ if (!intel_hid_execute_method(handle, INTEL_HID_DSM_HDSM_FN,
+ enable)) {
+ dev_warn(device, "failed to %sable hotkeys\n",
+ enable ? "en" : "dis");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void intel_button_array_enable(struct device *device, bool enable)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(device);
+ acpi_handle handle = ACPI_HANDLE(device);
+ unsigned long long button_cap;
+ acpi_status status;
+
+ if (!priv->array)
+ return;
+
+ /* Query supported platform features */
+ status = acpi_evaluate_integer(handle, "BTNC", NULL, &button_cap);
+ if (ACPI_FAILURE(status)) {
+ dev_warn(device, "failed to get button capability\n");
+ return;
+ }
+
+ /* Enable|disable features - power button is always enabled */
+ if (!intel_hid_execute_method(handle, INTEL_HID_DSM_BTNE_FN,
+ enable ? button_cap : 1))
+ dev_warn(device, "failed to set button capability\n");
+}
+
+static int intel_hid_pm_prepare(struct device *device)
+{
+ if (device_may_wakeup(device)) {
+ struct intel_hid_priv *priv = dev_get_drvdata(device);
+
+ priv->wakeup_mode = true;
+ }
+ return 0;
+}
+
+static void intel_hid_pm_complete(struct device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(device);
+
+ priv->wakeup_mode = false;
+}
+
+static int intel_hid_pl_suspend_handler(struct device *device)
+{
+ intel_button_array_enable(device, false);
+
+ if (!pm_suspend_no_platform())
+ intel_hid_set_enable(device, false);
+
+ return 0;
+}
+
+static int intel_hid_pl_resume_handler(struct device *device)
+{
+ intel_hid_pm_complete(device);
+
+ if (!pm_suspend_no_platform())
+ intel_hid_set_enable(device, true);
+
+ intel_button_array_enable(device, true);
+ return 0;
+}
+
+static const struct dev_pm_ops intel_hid_pl_pm_ops = {
+ .prepare = intel_hid_pm_prepare,
+ .complete = intel_hid_pm_complete,
+ .freeze = intel_hid_pl_suspend_handler,
+ .thaw = intel_hid_pl_resume_handler,
+ .restore = intel_hid_pl_resume_handler,
+ .suspend = intel_hid_pl_suspend_handler,
+ .resume = intel_hid_pl_resume_handler,
+};
+
+static int intel_hid_input_setup(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+ int ret;
+
+ priv->input_dev = devm_input_allocate_device(&device->dev);
+ if (!priv->input_dev)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL);
+ if (ret)
+ return ret;
+
+ priv->input_dev->name = "Intel HID events";
+ priv->input_dev->id.bustype = BUS_HOST;
+
+ return input_register_device(priv->input_dev);
+}
+
+static int intel_button_array_input_setup(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+ int ret;
+
+ /* Setup input device for 5 button array */
+ priv->array = devm_input_allocate_device(&device->dev);
+ if (!priv->array)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL);
+ if (ret)
+ return ret;
+
+ priv->array->name = "Intel HID 5 button array";
+ priv->array->id.bustype = BUS_HOST;
+
+ return input_register_device(priv->array);
+}
+
+static int intel_hid_switches_setup(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+
+ /* Setup input device for switches */
+ priv->switches = devm_input_allocate_device(&device->dev);
+ if (!priv->switches)
+ return -ENOMEM;
+
+ __set_bit(EV_SW, priv->switches->evbit);
+ __set_bit(SW_TABLET_MODE, priv->switches->swbit);
+
+ priv->switches->name = "Intel HID switches";
+ priv->switches->id.bustype = BUS_HOST;
+ return input_register_device(priv->switches);
+}
+
+static void report_tablet_mode_state(struct platform_device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ unsigned long long vgbs;
+ int m;
+
+ if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs))
+ return;
+
+ m = !(vgbs & TABLET_MODE_FLAG);
+ input_report_switch(priv->switches, SW_TABLET_MODE, m);
+ input_sync(priv->switches);
+}
+
+static bool report_tablet_mode_event(struct input_dev *input_dev, u32 event)
+{
+ if (!input_dev)
+ return false;
+
+ switch (event) {
+ case 0xcc:
+ input_report_switch(input_dev, SW_TABLET_MODE, 1);
+ input_sync(input_dev);
+ return true;
+ case 0xcd:
+ input_report_switch(input_dev, SW_TABLET_MODE, 0);
+ input_sync(input_dev);
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void notify_handler(acpi_handle handle, u32 event, void *context)
+{
+ struct platform_device *device = context;
+ struct intel_hid_priv *priv = dev_get_drvdata(&device->dev);
+ unsigned long long ev_index;
+ int err;
+
+ /*
+ * Some convertible have unreliable VGBS return which could cause incorrect
+ * SW_TABLET_MODE report, in these cases we enable support when receiving
+ * the first event instead of during driver setup.
+ *
+ * See dual_accel_detect.h for more info on the dual_accel check.
+ */
+ if (!priv->switches && !priv->dual_accel && (event == 0xcc || event == 0xcd)) {
+ dev_info(&device->dev, "switch event received, enable switches supports\n");
+ err = intel_hid_switches_setup(device);
+ if (err)
+ pr_err("Failed to setup Intel HID switches\n");
+ }
+
+ if (priv->wakeup_mode) {
+ /*
+ * Needed for wakeup from suspend-to-idle to work on some
+ * platforms that don't expose the 5-button array, but still
+ * send notifies with the power button event code to this
+ * device object on power button actions while suspended.
+ */
+ if (event == 0xce)
+ goto wakeup;
+
+ /*
+ * Some devices send (duplicate) tablet-mode events when moved
+ * around even though the mode has not changed; and they do this
+ * even when suspended.
+ * Update the switch state in case it changed and then return
+ * without waking up to avoid spurious wakeups.
+ */
+ if (event == 0xcc || event == 0xcd) {
+ report_tablet_mode_event(priv->switches, event);
+ return;
+ }
+
+ /* Wake up on 5-button array events only. */
+ if (event == 0xc0 || !priv->array)
+ return;
+
+ if (!sparse_keymap_entry_from_scancode(priv->array, event)) {
+ dev_info(&device->dev, "unknown event 0x%x\n", event);
+ return;
+ }
+
+wakeup:
+ pm_wakeup_hard_event(&device->dev);
+
+ return;
+ }
+
+ /*
+ * Needed for suspend to work on some platforms that don't expose
+ * the 5-button array, but still send notifies with power button
+ * event code to this device object on power button actions.
+ *
+ * Report the power button press and release.
+ */
+ if (!priv->array) {
+ if (event == 0xce) {
+ input_report_key(priv->input_dev, KEY_POWER, 1);
+ input_sync(priv->input_dev);
+ return;
+ }
+
+ if (event == 0xcf) {
+ input_report_key(priv->input_dev, KEY_POWER, 0);
+ input_sync(priv->input_dev);
+ return;
+ }
+ }
+
+ if (report_tablet_mode_event(priv->switches, event))
+ return;
+
+ /* 0xC0 is for HID events, other values are for 5 button array */
+ if (event != 0xc0) {
+ if (!priv->array ||
+ !sparse_keymap_report_event(priv->array, event, 1, true))
+ dev_dbg(&device->dev, "unknown event 0x%x\n", event);
+ return;
+ }
+
+ if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDEM_FN,
+ &ev_index)) {
+ dev_warn(&device->dev, "failed to get event index\n");
+ return;
+ }
+
+ if (!sparse_keymap_report_event(priv->input_dev, ev_index, 1, true))
+ dev_dbg(&device->dev, "unknown event index 0x%llx\n",
+ ev_index);
+}
+
+static bool button_array_present(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ unsigned long long event_cap;
+
+ if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V2_FN,
+ &event_cap)) {
+ /* Check presence of 5 button array or v2 power button */
+ if (event_cap & 0x60000)
+ return true;
+ }
+
+ if (intel_hid_evaluate_method(handle, INTEL_HID_DSM_HEBC_V1_FN,
+ &event_cap)) {
+ if (event_cap & 0x20000)
+ return true;
+ }
+
+ if (dmi_check_system(button_array_table))
+ return true;
+
+ return false;
+}
+
+static int intel_hid_probe(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ unsigned long long mode;
+ struct intel_hid_priv *priv;
+ acpi_status status;
+ int err;
+
+ intel_hid_init_dsm(handle);
+
+ if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) {
+ dev_warn(&device->dev, "failed to read mode\n");
+ return -ENODEV;
+ }
+
+ if (mode != 0) {
+ /*
+ * This driver only implements "simple" mode. There appear
+ * to be no other modes, but we should be paranoid and check
+ * for compatibility.
+ */
+ dev_info(&device->dev, "platform is not in simple mode\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ dev_set_drvdata(&device->dev, priv);
+
+ priv->dual_accel = dual_accel_detect();
+
+ err = intel_hid_input_setup(device);
+ if (err) {
+ pr_err("Failed to setup Intel HID hotkeys\n");
+ return err;
+ }
+
+ /* Setup 5 button array */
+ if (button_array_present(device)) {
+ dev_info(&device->dev, "platform supports 5 button array\n");
+ err = intel_button_array_input_setup(device);
+ if (err)
+ pr_err("Failed to setup Intel 5 button array hotkeys\n");
+ }
+
+ /* Setup switches for devices that we know VGBS return correctly */
+ if (dmi_check_system(dmi_vgbs_allow_list)) {
+ dev_info(&device->dev, "platform supports switches\n");
+ err = intel_hid_switches_setup(device);
+ if (err)
+ pr_err("Failed to setup Intel HID switches\n");
+ else
+ report_tablet_mode_state(device);
+ }
+
+ status = acpi_install_notify_handler(handle,
+ ACPI_DEVICE_NOTIFY,
+ notify_handler,
+ device);
+ if (ACPI_FAILURE(status))
+ return -EBUSY;
+
+ err = intel_hid_set_enable(&device->dev, true);
+ if (err)
+ goto err_remove_notify;
+
+ if (priv->array) {
+ unsigned long long dummy;
+
+ intel_button_array_enable(&device->dev, true);
+
+ /* Call button load method to enable HID power button */
+ if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_BTNL_FN,
+ &dummy)) {
+ dev_warn(&device->dev,
+ "failed to enable HID power button\n");
+ }
+ }
+
+ device_init_wakeup(&device->dev, true);
+ /*
+ * In order for system wakeup to work, the EC GPE has to be marked as
+ * a wakeup one, so do that here (this setting will persist, but it has
+ * no effect until the wakeup mask is set for the EC GPE).
+ */
+ acpi_ec_mark_gpe_for_wake();
+ return 0;
+
+err_remove_notify:
+ acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
+
+ return err;
+}
+
+static int intel_hid_remove(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+
+ device_init_wakeup(&device->dev, false);
+ acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
+ intel_hid_set_enable(&device->dev, false);
+ intel_button_array_enable(&device->dev, false);
+
+ /*
+ * Even if we failed to shut off the event stream, we can still
+ * safely detach from the device.
+ */
+ return 0;
+}
+
+static struct platform_driver intel_hid_pl_driver = {
+ .driver = {
+ .name = "intel-hid",
+ .acpi_match_table = intel_hid_ids,
+ .pm = &intel_hid_pl_pm_ops,
+ },
+ .probe = intel_hid_probe,
+ .remove = intel_hid_remove,
+};
+
+/*
+ * Unfortunately, some laptops provide a _HID="INT33D5" device with
+ * _CID="PNP0C02". This causes the pnpacpi scan driver to claim the
+ * ACPI node, so no platform device will be created. The pnpacpi
+ * driver rejects this device in subsequent processing, so no physical
+ * node is created at all.
+ *
+ * As a workaround until the ACPI core figures out how to handle
+ * this corner case, manually ask the ACPI platform device code to
+ * claim the ACPI node.
+ */
+static acpi_status __init
+check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+ const struct acpi_device_id *ids = context;
+ struct acpi_device *dev;
+
+ if (acpi_bus_get_device(handle, &dev) != 0)
+ return AE_OK;
+
+ if (acpi_match_device_ids(dev, ids) == 0)
+ if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
+ dev_info(&dev->dev,
+ "intel-hid: created platform device\n");
+
+ return AE_OK;
+}
+
+static int __init intel_hid_init(void)
+{
+ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX, check_acpi_dev, NULL,
+ (void *)intel_hid_ids, NULL);
+
+ return platform_driver_register(&intel_hid_pl_driver);
+}
+module_init(intel_hid_init);
+
+static void __exit intel_hid_exit(void)
+{
+ platform_driver_unregister(&intel_hid_pl_driver);
+}
+module_exit(intel_hid_exit);
diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c
new file mode 100644
index 000000000000..569342aa8926
--- /dev/null
+++ b/drivers/platform/x86/intel/int0002_vgpio.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel INT0002 "Virtual GPIO" driver
+ *
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Loosely based on android x86 kernel code which is:
+ *
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * Author: Dyut Kumar Sil <dyut.k.sil@intel.com>
+ *
+ * Some peripherals on Bay Trail and Cherry Trail platforms signal a Power
+ * Management Event (PME) to the Power Management Controller (PMC) to wakeup
+ * the system. When this happens software needs to clear the PME bus 0 status
+ * bit in the GPE0a_STS register to avoid an IRQ storm on IRQ 9.
+ *
+ * This is modelled in ACPI through the INT0002 ACPI device, which is
+ * called a "Virtual GPIO controller" in ACPI because it defines the event
+ * handler to call when the PME triggers through _AEI and _L02 / _E02
+ * methods as would be done for a real GPIO interrupt in ACPI. Note this
+ * is a hack to define an AML event handler for the PME while using existing
+ * ACPI mechanisms, this is not a real GPIO at all.
+ *
+ * This driver will bind to the INT0002 device, and register as a GPIO
+ * controller, letting gpiolib-acpi.c call the _L02 handler as it would
+ * for a real GPIO controller.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitmap.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#define DRV_NAME "INT0002 Virtual GPIO"
+
+/* For some reason the virtual GPIO pin tied to the GPE is numbered pin 2 */
+#define GPE0A_PME_B0_VIRT_GPIO_PIN 2
+
+#define GPE0A_PME_B0_STS_BIT BIT(13)
+#define GPE0A_PME_B0_EN_BIT BIT(13)
+#define GPE0A_STS_PORT 0x420
+#define GPE0A_EN_PORT 0x428
+
+struct int0002_data {
+ struct gpio_chip chip;
+ int parent_irq;
+ int wake_enable_count;
+};
+
+/*
+ * As this is not a real GPIO at all, but just a hack to model an event in
+ * ACPI the get / set functions are dummy functions.
+ */
+
+static int int0002_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return 0;
+}
+
+static void int0002_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+}
+
+static int int0002_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ return 0;
+}
+
+static void int0002_irq_ack(struct irq_data *data)
+{
+ outl(GPE0A_PME_B0_STS_BIT, GPE0A_STS_PORT);
+}
+
+static void int0002_irq_unmask(struct irq_data *data)
+{
+ u32 gpe_en_reg;
+
+ gpe_en_reg = inl(GPE0A_EN_PORT);
+ gpe_en_reg |= GPE0A_PME_B0_EN_BIT;
+ outl(gpe_en_reg, GPE0A_EN_PORT);
+}
+
+static void int0002_irq_mask(struct irq_data *data)
+{
+ u32 gpe_en_reg;
+
+ gpe_en_reg = inl(GPE0A_EN_PORT);
+ gpe_en_reg &= ~GPE0A_PME_B0_EN_BIT;
+ outl(gpe_en_reg, GPE0A_EN_PORT);
+}
+
+static int int0002_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct int0002_data *int0002 = container_of(chip, struct int0002_data, chip);
+
+ /*
+ * Applying of the wakeup flag to our parent IRQ is delayed till system
+ * suspend, because we only want to do this when using s2idle.
+ */
+ if (on)
+ int0002->wake_enable_count++;
+ else
+ int0002->wake_enable_count--;
+
+ return 0;
+}
+
+static irqreturn_t int0002_irq(int irq, void *data)
+{
+ struct gpio_chip *chip = data;
+ u32 gpe_sts_reg;
+
+ gpe_sts_reg = inl(GPE0A_STS_PORT);
+ if (!(gpe_sts_reg & GPE0A_PME_B0_STS_BIT))
+ return IRQ_NONE;
+
+ generic_handle_irq(irq_find_mapping(chip->irq.domain,
+ GPE0A_PME_B0_VIRT_GPIO_PIN));
+
+ pm_wakeup_hard_event(chip->parent);
+
+ return IRQ_HANDLED;
+}
+
+static bool int0002_check_wake(void *data)
+{
+ u32 gpe_sts_reg;
+
+ gpe_sts_reg = inl(GPE0A_STS_PORT);
+ return (gpe_sts_reg & GPE0A_PME_B0_STS_BIT);
+}
+
+static struct irq_chip int0002_irqchip = {
+ .name = DRV_NAME,
+ .irq_ack = int0002_irq_ack,
+ .irq_mask = int0002_irq_mask,
+ .irq_unmask = int0002_irq_unmask,
+ .irq_set_wake = int0002_irq_set_wake,
+};
+
+static const struct x86_cpu_id int0002_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL),
+ {}
+};
+
+static void int0002_init_irq_valid_mask(struct gpio_chip *chip,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ bitmap_clear(valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN);
+}
+
+static int int0002_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct x86_cpu_id *cpu_id;
+ struct int0002_data *int0002;
+ struct gpio_irq_chip *girq;
+ struct gpio_chip *chip;
+ int irq, ret;
+
+ /* Menlow has a different INT0002 device? <sigh> */
+ cpu_id = x86_match_cpu(int0002_cpu_ids);
+ if (!cpu_id)
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ int0002 = devm_kzalloc(dev, sizeof(*int0002), GFP_KERNEL);
+ if (!int0002)
+ return -ENOMEM;
+
+ int0002->parent_irq = irq;
+
+ chip = &int0002->chip;
+ chip->label = DRV_NAME;
+ chip->parent = dev;
+ chip->owner = THIS_MODULE;
+ chip->get = int0002_gpio_get;
+ chip->set = int0002_gpio_set;
+ chip->direction_input = int0002_gpio_get;
+ chip->direction_output = int0002_gpio_direction_output;
+ chip->base = -1;
+ chip->ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1;
+ chip->irq.init_valid_mask = int0002_init_irq_valid_mask;
+
+ /*
+ * We directly request the irq here instead of passing a flow-handler
+ * to gpiochip_set_chained_irqchip, because the irq is shared.
+ * FIXME: augment this if we managed to pull handling of shared
+ * IRQs into gpiolib.
+ */
+ ret = devm_request_irq(dev, irq, int0002_irq,
+ IRQF_SHARED, "INT0002", chip);
+ if (ret) {
+ dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret);
+ return ret;
+ }
+
+ girq = &chip->irq;
+ girq->chip = &int0002_irqchip;
+ /* This let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_edge_irq;
+
+ ret = devm_gpiochip_add_data(dev, chip, NULL);
+ if (ret) {
+ dev_err(dev, "Error adding gpio chip: %d\n", ret);
+ return ret;
+ }
+
+ acpi_register_wakeup_handler(irq, int0002_check_wake, NULL);
+ device_init_wakeup(dev, true);
+ dev_set_drvdata(dev, int0002);
+ return 0;
+}
+
+static int int0002_remove(struct platform_device *pdev)
+{
+ device_init_wakeup(&pdev->dev, false);
+ acpi_unregister_wakeup_handler(int0002_check_wake, NULL);
+ return 0;
+}
+
+static int int0002_suspend(struct device *dev)
+{
+ struct int0002_data *int0002 = dev_get_drvdata(dev);
+
+ /*
+ * The INT0002 parent IRQ is often shared with the ACPI GPE IRQ, don't
+ * muck with it when firmware based suspend is used, otherwise we may
+ * cause spurious wakeups from firmware managed suspend.
+ */
+ if (!pm_suspend_via_firmware() && int0002->wake_enable_count)
+ enable_irq_wake(int0002->parent_irq);
+
+ return 0;
+}
+
+static int int0002_resume(struct device *dev)
+{
+ struct int0002_data *int0002 = dev_get_drvdata(dev);
+
+ if (!pm_suspend_via_firmware() && int0002->wake_enable_count)
+ disable_irq_wake(int0002->parent_irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops int0002_pm_ops = {
+ .suspend = int0002_suspend,
+ .resume = int0002_resume,
+};
+
+static const struct acpi_device_id int0002_acpi_ids[] = {
+ { "INT0002", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, int0002_acpi_ids);
+
+static struct platform_driver int0002_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .acpi_match_table = int0002_acpi_ids,
+ .pm = &int0002_pm_ops,
+ },
+ .probe = int0002_probe,
+ .remove = int0002_remove,
+};
+
+module_platform_driver(int0002_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/int1092/Kconfig b/drivers/platform/x86/intel/int1092/Kconfig
new file mode 100644
index 000000000000..2e9a177241aa
--- /dev/null
+++ b/drivers/platform/x86/intel/int1092/Kconfig
@@ -0,0 +1,14 @@
+config INTEL_SAR_INT1092
+ tristate "Intel Specific Absorption Rate Driver"
+ depends on ACPI
+ help
+ This driver helps to limit the exposure of human body to RF frequency by
+ providing information to userspace application that will inform the Intel
+ M.2 modem to regulate the RF power based on SAR data obtained from the
+ sensors captured in the BIOS. ACPI interface exposes this data from the BIOS
+ to SAR driver. The front end application in userspace will interact with SAR
+ driver to obtain information like the device mode, Antenna index, baseband index,
+ SAR table index and use available communication like MBIM interface to enable
+ data communication to modem for RF power regulation. Enable this config when
+ given platform needs to support "Dynamic SAR" configuration for a modem available
+ on the platform.
diff --git a/drivers/platform/x86/intel/int1092/Makefile b/drivers/platform/x86/intel/int1092/Makefile
new file mode 100644
index 000000000000..4ab94e541de3
--- /dev/null
+++ b/drivers/platform/x86/intel/int1092/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_INTEL_SAR_INT1092) += intel_sar.o
diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c
new file mode 100644
index 000000000000..379560fe5df9
--- /dev/null
+++ b/drivers/platform/x86/intel/int1092/intel_sar.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kobject.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include "intel_sar.h"
+
+/**
+ * get_int_value: Retrieve integer values from ACPI Object
+ * @obj: acpi_object pointer which has the integer value
+ * @out: output pointer will get integer value
+ *
+ * Function is used to retrieve integer value from acpi object.
+ *
+ * Return:
+ * * 0 on success
+ * * -EIO if there is an issue in acpi_object passed.
+ */
+static int get_int_value(union acpi_object *obj, int *out)
+{
+ if (!obj || obj->type != ACPI_TYPE_INTEGER)
+ return -EIO;
+ *out = (int)obj->integer.value;
+ return 0;
+}
+
+/**
+ * update_sar_data: sar data is updated based on regulatory mode
+ * @context: pointer to driver context structure
+ *
+ * sar_data is updated based on regulatory value
+ * context->reg_value will never exceed MAX_REGULATORY
+ */
+static void update_sar_data(struct wwan_sar_context *context)
+{
+ struct wwan_device_mode_configuration *config =
+ &context->config_data[context->reg_value];
+
+ if (config->device_mode_info &&
+ context->sar_data.device_mode < config->total_dev_mode) {
+ struct wwan_device_mode_info *dev_mode =
+ &config->device_mode_info[context->sar_data.device_mode];
+
+ context->sar_data.antennatable_index = dev_mode->antennatable_index;
+ context->sar_data.bandtable_index = dev_mode->bandtable_index;
+ context->sar_data.sartable_index = dev_mode->sartable_index;
+ }
+}
+
+/**
+ * parse_package: parse acpi package for retrieving SAR information
+ * @context: pointer to driver context structure
+ * @item : acpi_object pointer
+ *
+ * Given acpi_object is iterated to retrieve information for each device mode.
+ * If a given package corresponding to a specific device mode is faulty, it is
+ * skipped and the specific entry in context structure will have the default value
+ * of zero. Decoding of subsequent device modes is realized by having "continue"
+ * statements in the for loop on encountering error in parsing given device mode.
+ *
+ * Return:
+ * AE_OK if success
+ * AE_ERROR on error
+ */
+static acpi_status parse_package(struct wwan_sar_context *context, union acpi_object *item)
+{
+ struct wwan_device_mode_configuration *data;
+ int value, itr, reg;
+ union acpi_object *num;
+
+ num = &item->package.elements[0];
+ if (get_int_value(num, &value) || value < 0 || value >= MAX_REGULATORY)
+ return AE_ERROR;
+
+ reg = value;
+
+ data = &context->config_data[reg];
+ if (data->total_dev_mode > MAX_DEV_MODES || data->total_dev_mode == 0 ||
+ item->package.count <= data->total_dev_mode)
+ return AE_ERROR;
+
+ data->device_mode_info = kmalloc_array(data->total_dev_mode,
+ sizeof(struct wwan_device_mode_info), GFP_KERNEL);
+ if (!data->device_mode_info)
+ return AE_ERROR;
+
+ for (itr = 0; itr < data->total_dev_mode; itr++) {
+ struct wwan_device_mode_info temp = { 0 };
+
+ num = &item->package.elements[itr + 1];
+ if (num->type != ACPI_TYPE_PACKAGE || num->package.count < TOTAL_DATA)
+ continue;
+ if (get_int_value(&num->package.elements[0], &temp.device_mode))
+ continue;
+ if (get_int_value(&num->package.elements[1], &temp.bandtable_index))
+ continue;
+ if (get_int_value(&num->package.elements[2], &temp.antennatable_index))
+ continue;
+ if (get_int_value(&num->package.elements[3], &temp.sartable_index))
+ continue;
+ data->device_mode_info[itr] = temp;
+ }
+ return AE_OK;
+}
+
+/**
+ * sar_get_device_mode: Extraction of information from BIOS via DSM calls
+ * @device: ACPI device for which to retrieve the data
+ *
+ * Retrieve the current device mode information from the BIOS.
+ *
+ * Return:
+ * AE_OK on success
+ * AE_ERROR on error
+ */
+static acpi_status sar_get_device_mode(struct platform_device *device)
+{
+ struct wwan_sar_context *context = dev_get_drvdata(&device->dev);
+ acpi_status status = AE_OK;
+ union acpi_object *out;
+ u32 rev = 0;
+ int value;
+
+ out = acpi_evaluate_dsm(context->handle, &context->guid, rev,
+ COMMAND_ID_DEV_MODE, NULL);
+ if (get_int_value(out, &value)) {
+ dev_err(&device->dev, "DSM cmd:%d Failed to retrieve value\n", COMMAND_ID_DEV_MODE);
+ status = AE_ERROR;
+ goto dev_mode_error;
+ }
+ context->sar_data.device_mode = value;
+ update_sar_data(context);
+ sysfs_notify(&device->dev.kobj, NULL, SYSFS_DATANAME);
+
+dev_mode_error:
+ ACPI_FREE(out);
+ return status;
+}
+
+static const struct acpi_device_id sar_device_ids[] = {
+ { "INTC1092", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, sar_device_ids);
+
+static ssize_t intc_data_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct wwan_sar_context *context = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d %d %d %d\n", context->sar_data.device_mode,
+ context->sar_data.bandtable_index,
+ context->sar_data.antennatable_index,
+ context->sar_data.sartable_index);
+}
+static DEVICE_ATTR_RO(intc_data);
+
+static ssize_t intc_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct wwan_sar_context *context = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", context->reg_value);
+}
+
+static ssize_t intc_reg_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct wwan_sar_context *context = dev_get_drvdata(dev);
+ unsigned int value;
+ int read;
+
+ if (!count)
+ return -EINVAL;
+ read = kstrtouint(buf, 10, &value);
+ if (read < 0)
+ return read;
+ if (value >= MAX_REGULATORY)
+ return -EOVERFLOW;
+ context->reg_value = value;
+ update_sar_data(context);
+ sysfs_notify(&dev->kobj, NULL, SYSFS_DATANAME);
+ return count;
+}
+static DEVICE_ATTR_RW(intc_reg);
+
+static struct attribute *intcsar_attrs[] = {
+ &dev_attr_intc_data.attr,
+ &dev_attr_intc_reg.attr,
+ NULL
+};
+
+static struct attribute_group intcsar_group = {
+ .attrs = intcsar_attrs,
+};
+
+static void sar_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct platform_device *device = data;
+
+ if (event == SAR_EVENT) {
+ if (sar_get_device_mode(device) != AE_OK)
+ dev_err(&device->dev, "sar_get_device_mode error");
+ }
+}
+
+static void sar_get_data(int reg, struct wwan_sar_context *context)
+{
+ union acpi_object *out, req;
+ u32 rev = 0;
+
+ req.type = ACPI_TYPE_INTEGER;
+ req.integer.value = reg;
+ out = acpi_evaluate_dsm(context->handle, &context->guid, rev,
+ COMMAND_ID_CONFIG_TABLE, &req);
+ if (!out)
+ return;
+ if (out->type == ACPI_TYPE_PACKAGE && out->package.count >= 3 &&
+ out->package.elements[0].type == ACPI_TYPE_INTEGER &&
+ out->package.elements[1].type == ACPI_TYPE_INTEGER &&
+ out->package.elements[2].type == ACPI_TYPE_PACKAGE &&
+ out->package.elements[2].package.count > 0) {
+ context->config_data[reg].version = out->package.elements[0].integer.value;
+ context->config_data[reg].total_dev_mode =
+ out->package.elements[1].integer.value;
+ if (context->config_data[reg].total_dev_mode <= 0 ||
+ context->config_data[reg].total_dev_mode > MAX_DEV_MODES) {
+ ACPI_FREE(out);
+ return;
+ }
+ parse_package(context, &out->package.elements[2]);
+ }
+ ACPI_FREE(out);
+}
+
+static int sar_probe(struct platform_device *device)
+{
+ struct wwan_sar_context *context;
+ int reg;
+ int result;
+
+ context = kzalloc(sizeof(*context), GFP_KERNEL);
+ if (!context)
+ return -ENOMEM;
+
+ context->sar_device = device;
+ context->handle = ACPI_HANDLE(&device->dev);
+ dev_set_drvdata(&device->dev, context);
+
+ result = guid_parse(SAR_DSM_UUID, &context->guid);
+ if (result) {
+ dev_err(&device->dev, "SAR UUID parse error: %d\n", result);
+ goto r_free;
+ }
+
+ for (reg = 0; reg < MAX_REGULATORY; reg++)
+ sar_get_data(reg, context);
+
+ if (sar_get_device_mode(device) != AE_OK) {
+ dev_err(&device->dev, "Failed to get device mode\n");
+ result = -EIO;
+ goto r_free;
+ }
+
+ result = sysfs_create_group(&device->dev.kobj, &intcsar_group);
+ if (result) {
+ dev_err(&device->dev, "sysfs creation failed\n");
+ goto r_free;
+ }
+
+ if (acpi_install_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY,
+ sar_notify, (void *)device) != AE_OK) {
+ dev_err(&device->dev, "Failed acpi_install_notify_handler\n");
+ result = -EIO;
+ goto r_sys;
+ }
+ return 0;
+
+r_sys:
+ sysfs_remove_group(&device->dev.kobj, &intcsar_group);
+r_free:
+ kfree(context);
+ return result;
+}
+
+static int sar_remove(struct platform_device *device)
+{
+ struct wwan_sar_context *context = dev_get_drvdata(&device->dev);
+ int reg;
+
+ acpi_remove_notify_handler(ACPI_HANDLE(&device->dev),
+ ACPI_DEVICE_NOTIFY, sar_notify);
+ sysfs_remove_group(&device->dev.kobj, &intcsar_group);
+ for (reg = 0; reg < MAX_REGULATORY; reg++)
+ kfree(context->config_data[reg].device_mode_info);
+
+ kfree(context);
+ return 0;
+}
+
+static struct platform_driver sar_driver = {
+ .probe = sar_probe,
+ .remove = sar_remove,
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ .acpi_match_table = ACPI_PTR(sar_device_ids)
+ }
+};
+module_platform_driver(sar_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Platform device driver for INTEL MODEM BIOS SAR");
+MODULE_AUTHOR("Shravan S <s.shravan@intel.com>");
diff --git a/drivers/platform/x86/intel/int1092/intel_sar.h b/drivers/platform/x86/intel/int1092/intel_sar.h
new file mode 100644
index 000000000000..b5310510b84c
--- /dev/null
+++ b/drivers/platform/x86/intel/int1092/intel_sar.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2021, Intel Corporation.
+ */
+#ifndef INTEL_SAR_H
+#define INTEL_SAR_H
+
+#define COMMAND_ID_DEV_MODE 1
+#define COMMAND_ID_CONFIG_TABLE 2
+#define DRVNAME "intc_sar"
+#define MAX_DEV_MODES 50
+#define MAX_REGULATORY 3
+#define SAR_DSM_UUID "82737E72-3A33-4C45-A9C7-57C0411A5F13"
+#define SAR_EVENT 0x80
+#define SYSFS_DATANAME "intc_data"
+#define TOTAL_DATA 4
+
+/**
+ * Structure wwan_device_mode_info - device mode information
+ * Holds the data that needs to be passed to userspace.
+ * The data is updated from the BIOS sensor information.
+ * @device_mode: Specific mode of the device
+ * @bandtable_index: Index of RF band
+ * @antennatable_index: Index of antenna
+ * @sartable_index: Index of SAR
+ */
+struct wwan_device_mode_info {
+ int device_mode;
+ int bandtable_index;
+ int antennatable_index;
+ int sartable_index;
+};
+
+/**
+ * Structure wwan_device_mode_configuration - device configuration
+ * Holds the data that is configured and obtained on probe event.
+ * The data is updated from the BIOS sensor information.
+ * @version: Mode configuration version
+ * @total_dev_mode: Total number of device modes
+ * @device_mode_info: pointer to structure wwan_device_mode_info
+ */
+struct wwan_device_mode_configuration {
+ int version;
+ int total_dev_mode;
+ struct wwan_device_mode_info *device_mode_info;
+};
+
+/**
+ * Structure wwan_supported_info - userspace datastore
+ * Holds the data that is obtained from userspace
+ * The data is updated from the userspace and send value back in the
+ * structure format that is mentioned here.
+ * @reg_mode_needed: regulatory mode set by user for tests
+ * @bios_table_revision: Version of SAR table
+ * @num_supported_modes: Total supported modes based on reg_mode
+ */
+struct wwan_supported_info {
+ int reg_mode_needed;
+ int bios_table_revision;
+ int num_supported_modes;
+};
+
+/**
+ * Structure wwan_sar_context - context of SAR
+ * Holds the complete context as long as the driver is in existence
+ * The context holds instance of the data used for different cases.
+ * @guid: Group id
+ * @handle: store acpi handle
+ * @reg_value: regulatory value
+ * Regulatory 0: FCC, 1: CE, 2: ISED
+ * @sar_device: platform_device type
+ * @sar_kobject: kobject for sysfs
+ * @supported_data: wwan_supported_info struct
+ * @sar_data: wwan_device_mode_info struct
+ * @config_data: wwan_device_mode_configuration array struct
+ */
+struct wwan_sar_context {
+ guid_t guid;
+ acpi_handle handle;
+ int reg_value;
+ struct platform_device *sar_device;
+ struct wwan_supported_info supported_data;
+ struct wwan_device_mode_info sar_data;
+ struct wwan_device_mode_configuration config_data[MAX_REGULATORY];
+};
+#endif /* INTEL_SAR_H */
diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile
index cc11183ce179..9456e3b37f6f 100644
--- a/drivers/platform/x86/intel/int33fe/Makefile
+++ b/drivers/platform/x86/intel/int33fe/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
-intel_cht_int33fe-objs := intel_cht_int33fe_common.o \
+intel_cht_int33fe-y := intel_cht_int33fe_common.o \
intel_cht_int33fe_typec.o \
intel_cht_int33fe_microb.o
diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c
index 251ed9bac789..463222521e61 100644
--- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c
+++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c
@@ -16,33 +16,6 @@
#define EXPECTED_PTYPE 4
-static int cht_int33fe_i2c_res_filter(struct acpi_resource *ares, void *data)
-{
- struct acpi_resource_i2c_serialbus *sb;
- int *count = data;
-
- if (i2c_acpi_get_i2c_resource(ares, &sb))
- (*count)++;
-
- return 1;
-}
-
-static int cht_int33fe_count_i2c_clients(struct device *dev)
-{
- struct acpi_device *adev = ACPI_COMPANION(dev);
- LIST_HEAD(resource_list);
- int count = 0;
- int ret;
-
- ret = acpi_dev_get_resources(adev, &resource_list,
- cht_int33fe_i2c_res_filter, &count);
- acpi_dev_free_resource_list(&resource_list);
- if (ret < 0)
- return ret;
-
- return count;
-}
-
static int cht_int33fe_check_hw_type(struct device *dev)
{
unsigned long long ptyp;
@@ -69,7 +42,7 @@ static int cht_int33fe_check_hw_type(struct device *dev)
return -ENODEV;
}
- ret = cht_int33fe_count_i2c_clients(dev);
+ ret = i2c_acpi_client_count(ACPI_COMPANION(dev));
if (ret < 0)
return ret;
diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile
index 48bd97f0a04e..2362e04db18d 100644
--- a/drivers/platform/x86/intel/int3472/Makefile
+++ b/drivers/platform/x86/intel/int3472/Makefile
@@ -1,5 +1,5 @@
obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o
-intel_skl_int3472-objs := intel_skl_int3472_common.o \
+intel_skl_int3472-y := intel_skl_int3472_common.o \
intel_skl_int3472_discrete.o \
intel_skl_int3472_tps68470.o \
intel_skl_int3472_clk_and_regulator.o
diff --git a/drivers/platform/x86/intel/mrfld_pwrbtn.c b/drivers/platform/x86/intel/mrfld_pwrbtn.c
new file mode 100644
index 000000000000..d58fea51747e
--- /dev/null
+++ b/drivers/platform/x86/intel/mrfld_pwrbtn.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Power-button driver for Basin Cove PMIC
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ */
+
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/mfd/intel_soc_pmic_mrfld.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/slab.h>
+
+#define BCOVE_PBSTATUS 0x27
+#define BCOVE_PBSTATUS_PBLVL BIT(4) /* 1 - release, 0 - press */
+
+static irqreturn_t mrfld_pwrbtn_interrupt(int irq, void *dev_id)
+{
+ struct input_dev *input = dev_id;
+ struct device *dev = input->dev.parent;
+ struct regmap *regmap = dev_get_drvdata(dev);
+ unsigned int state;
+ int ret;
+
+ ret = regmap_read(regmap, BCOVE_PBSTATUS, &state);
+ if (ret)
+ return IRQ_NONE;
+
+ dev_dbg(dev, "PBSTATUS=0x%x\n", state);
+ input_report_key(input, KEY_POWER, !(state & BCOVE_PBSTATUS_PBLVL));
+ input_sync(input);
+
+ regmap_update_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_PWRBTN, 0);
+ return IRQ_HANDLED;
+}
+
+static int mrfld_pwrbtn_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent);
+ struct input_dev *input;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+ input->name = pdev->name;
+ input->phys = "power-button/input0";
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = dev;
+ input_set_capability(input, EV_KEY, KEY_POWER);
+ ret = input_register_device(input);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(dev, pmic->regmap);
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_pwrbtn_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED, pdev->name,
+ input);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(pmic->regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_PWRBTN, 0);
+ regmap_update_bits(pmic->regmap, BCOVE_MPBIRQ, BCOVE_PBIRQ_PBTN, 0);
+
+ device_init_wakeup(dev, true);
+ dev_pm_set_wake_irq(dev, irq);
+ return 0;
+}
+
+static int mrfld_pwrbtn_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ dev_pm_clear_wake_irq(dev);
+ device_init_wakeup(dev, false);
+ return 0;
+}
+
+static const struct platform_device_id mrfld_pwrbtn_id_table[] = {
+ { .name = "mrfld_bcove_pwrbtn" },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, mrfld_pwrbtn_id_table);
+
+static struct platform_driver mrfld_pwrbtn_driver = {
+ .driver = {
+ .name = "mrfld_bcove_pwrbtn",
+ },
+ .probe = mrfld_pwrbtn_probe,
+ .remove = mrfld_pwrbtn_remove,
+ .id_table = mrfld_pwrbtn_id_table,
+};
+module_platform_driver(mrfld_pwrbtn_driver);
+
+MODULE_DESCRIPTION("Power-button driver for Basin Cove PMIC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/oaktrail.c b/drivers/platform/x86/intel/oaktrail.c
new file mode 100644
index 000000000000..1a09a75bd16d
--- /dev/null
+++ b/drivers/platform/x86/intel/oaktrail.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel OakTrail Platform support
+ *
+ * Copyright (C) 2010-2011 Intel Corporation
+ * Author: Yin Kangkai (kangkai.yin@intel.com)
+ *
+ * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz
+ * <cezary.jackiewicz (at) gmail.com>, based on MSI driver
+ * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+ *
+ * This driver does below things:
+ * 1. registers itself in the Linux backlight control in
+ * /sys/class/backlight/intel_oaktrail/
+ *
+ * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/
+ * for these components: wifi, bluetooth, wwan (3g), gps
+ *
+ * This driver might work on other products based on Oaktrail. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * product as compatible.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+
+#include <acpi/video.h>
+
+#define DRIVER_NAME "intel_oaktrail"
+#define DRIVER_VERSION "0.4ac1"
+
+/*
+ * This is the devices status address in EC space, and the control bits
+ * definition:
+ *
+ * (1 << 0): Camera enable/disable, RW.
+ * (1 << 1): Bluetooth enable/disable, RW.
+ * (1 << 2): GPS enable/disable, RW.
+ * (1 << 3): WiFi enable/disable, RW.
+ * (1 << 4): WWAN (3G) enable/disable, RW.
+ * (1 << 5): Touchscreen enable/disable, Read Only.
+ */
+#define OT_EC_DEVICE_STATE_ADDRESS 0xD6
+
+#define OT_EC_CAMERA_MASK (1 << 0)
+#define OT_EC_BT_MASK (1 << 1)
+#define OT_EC_GPS_MASK (1 << 2)
+#define OT_EC_WIFI_MASK (1 << 3)
+#define OT_EC_WWAN_MASK (1 << 4)
+#define OT_EC_TS_MASK (1 << 5)
+
+/*
+ * This is the address in EC space and commands used to control LCD backlight:
+ *
+ * Two steps needed to change the LCD backlight:
+ * 1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS;
+ * 2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS.
+ *
+ * To read the LCD back light, just read out the value from
+ * OT_EC_BL_BRIGHTNESS_ADDRESS.
+ *
+ * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX)
+ */
+#define OT_EC_BL_BRIGHTNESS_ADDRESS 0x44
+#define OT_EC_BL_BRIGHTNESS_MAX 100
+#define OT_EC_BL_CONTROL_ADDRESS 0x3A
+#define OT_EC_BL_CONTROL_ON_DATA 0x1A
+
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static struct platform_device *oaktrail_device;
+static struct backlight_device *oaktrail_bl_device;
+static struct rfkill *bt_rfkill;
+static struct rfkill *gps_rfkill;
+static struct rfkill *wifi_rfkill;
+static struct rfkill *wwan_rfkill;
+
+
+/* rfkill */
+static int oaktrail_rfkill_set(void *data, bool blocked)
+{
+ u8 value;
+ u8 result;
+ unsigned long radio = (unsigned long) data;
+
+ ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result);
+
+ if (!blocked)
+ value = (u8) (result | radio);
+ else
+ value = (u8) (result & ~radio);
+
+ ec_write(OT_EC_DEVICE_STATE_ADDRESS, value);
+
+ return 0;
+}
+
+static const struct rfkill_ops oaktrail_rfkill_ops = {
+ .set_block = oaktrail_rfkill_set,
+};
+
+static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type,
+ unsigned long mask)
+{
+ struct rfkill *rfkill_dev;
+ u8 value;
+ int err;
+
+ rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type,
+ &oaktrail_rfkill_ops, (void *)mask);
+ if (!rfkill_dev)
+ return ERR_PTR(-ENOMEM);
+
+ ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value);
+ rfkill_init_sw_state(rfkill_dev, (value & mask) != 1);
+
+ err = rfkill_register(rfkill_dev);
+ if (err) {
+ rfkill_destroy(rfkill_dev);
+ return ERR_PTR(err);
+ }
+
+ return rfkill_dev;
+}
+
+static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf)
+{
+ if (rf) {
+ rfkill_unregister(rf);
+ rfkill_destroy(rf);
+ }
+}
+
+static void oaktrail_rfkill_cleanup(void)
+{
+ __oaktrail_rfkill_cleanup(wifi_rfkill);
+ __oaktrail_rfkill_cleanup(bt_rfkill);
+ __oaktrail_rfkill_cleanup(gps_rfkill);
+ __oaktrail_rfkill_cleanup(wwan_rfkill);
+}
+
+static int oaktrail_rfkill_init(void)
+{
+ int ret;
+
+ wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi",
+ RFKILL_TYPE_WLAN,
+ OT_EC_WIFI_MASK);
+ if (IS_ERR(wifi_rfkill)) {
+ ret = PTR_ERR(wifi_rfkill);
+ wifi_rfkill = NULL;
+ goto cleanup;
+ }
+
+ bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth",
+ RFKILL_TYPE_BLUETOOTH,
+ OT_EC_BT_MASK);
+ if (IS_ERR(bt_rfkill)) {
+ ret = PTR_ERR(bt_rfkill);
+ bt_rfkill = NULL;
+ goto cleanup;
+ }
+
+ gps_rfkill = oaktrail_rfkill_new("oaktrail-gps",
+ RFKILL_TYPE_GPS,
+ OT_EC_GPS_MASK);
+ if (IS_ERR(gps_rfkill)) {
+ ret = PTR_ERR(gps_rfkill);
+ gps_rfkill = NULL;
+ goto cleanup;
+ }
+
+ wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan",
+ RFKILL_TYPE_WWAN,
+ OT_EC_WWAN_MASK);
+ if (IS_ERR(wwan_rfkill)) {
+ ret = PTR_ERR(wwan_rfkill);
+ wwan_rfkill = NULL;
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+ oaktrail_rfkill_cleanup();
+ return ret;
+}
+
+
+/* backlight */
+static int get_backlight_brightness(struct backlight_device *b)
+{
+ u8 value;
+ ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value);
+
+ return value;
+}
+
+static int set_backlight_brightness(struct backlight_device *b)
+{
+ u8 percent = (u8) b->props.brightness;
+ if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX)
+ return -EINVAL;
+
+ ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent);
+ ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA);
+
+ return 0;
+}
+
+static const struct backlight_ops oaktrail_bl_ops = {
+ .get_brightness = get_backlight_brightness,
+ .update_status = set_backlight_brightness,
+};
+
+static int oaktrail_backlight_init(void)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX;
+ bd = backlight_device_register(DRIVER_NAME,
+ &oaktrail_device->dev, NULL,
+ &oaktrail_bl_ops,
+ &props);
+
+ if (IS_ERR(bd)) {
+ oaktrail_bl_device = NULL;
+ pr_warn("Unable to register backlight device\n");
+ return PTR_ERR(bd);
+ }
+
+ oaktrail_bl_device = bd;
+
+ bd->props.brightness = get_backlight_brightness(bd);
+ bd->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(bd);
+
+ return 0;
+}
+
+static void oaktrail_backlight_exit(void)
+{
+ backlight_device_unregister(oaktrail_bl_device);
+}
+
+static int oaktrail_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static int oaktrail_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver oaktrail_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = oaktrail_probe,
+ .remove = oaktrail_remove,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+ pr_info("Identified model '%s'\n", id->ident);
+ return 0;
+}
+
+static const struct dmi_system_id oaktrail_dmi_table[] __initconst = {
+ {
+ .ident = "OakTrail platform",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"),
+ },
+ .callback = dmi_check_cb
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table);
+
+static int __init oaktrail_init(void)
+{
+ int ret;
+
+ if (acpi_disabled) {
+ pr_err("ACPI needs to be enabled for this driver to work!\n");
+ return -ENODEV;
+ }
+
+ if (!force && !dmi_check_system(oaktrail_dmi_table)) {
+ pr_err("Platform not recognized (You could try the module's force-parameter)");
+ return -ENODEV;
+ }
+
+ ret = platform_driver_register(&oaktrail_driver);
+ if (ret) {
+ pr_warn("Unable to register platform driver\n");
+ goto err_driver_reg;
+ }
+
+ oaktrail_device = platform_device_alloc(DRIVER_NAME, -1);
+ if (!oaktrail_device) {
+ pr_warn("Unable to allocate platform device\n");
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = platform_device_add(oaktrail_device);
+ if (ret) {
+ pr_warn("Unable to add platform device\n");
+ goto err_device_add;
+ }
+
+ if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
+ ret = oaktrail_backlight_init();
+ if (ret)
+ goto err_backlight;
+ }
+
+ ret = oaktrail_rfkill_init();
+ if (ret) {
+ pr_warn("Setup rfkill failed\n");
+ goto err_rfkill;
+ }
+
+ pr_info("Driver "DRIVER_VERSION" successfully loaded\n");
+ return 0;
+
+err_rfkill:
+ oaktrail_backlight_exit();
+err_backlight:
+ platform_device_del(oaktrail_device);
+err_device_add:
+ platform_device_put(oaktrail_device);
+err_device_alloc:
+ platform_driver_unregister(&oaktrail_driver);
+err_driver_reg:
+
+ return ret;
+}
+
+static void __exit oaktrail_cleanup(void)
+{
+ oaktrail_backlight_exit();
+ oaktrail_rfkill_cleanup();
+ platform_device_unregister(oaktrail_device);
+ platform_driver_unregister(&oaktrail_driver);
+
+ pr_info("Driver unloaded\n");
+}
+
+module_init(oaktrail_init);
+module_exit(oaktrail_cleanup);
+
+MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)");
+MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig
new file mode 100644
index 000000000000..b526597e4deb
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Intel x86 Platform-Specific Drivers
+#
+
+config INTEL_PMC_CORE
+ tristate "Intel PMC Core driver"
+ depends on PCI
+ depends on ACPI
+ help
+ The Intel Platform Controller Hub for Intel Core SoCs provides access
+ to Power Management Controller registers via various interfaces. This
+ driver can utilize debugging capabilities and supported features as
+ exposed by the Power Management Controller. It also may perform some
+ tasks in the PMC in order to enable transition into the SLPS0 state.
+ It should be selected on all Intel platforms supported by the driver.
+
+ Supported features:
+ - SLP_S0_RESIDENCY counter
+ - PCH IP Power Gating status
+ - LTR Ignore / LTR Show
+ - MPHY/PLL gating status (Sunrisepoint PCH only)
+ - SLPS0 Debug registers (Cannonlake/Icelake PCH)
+ - Low Power Mode registers (Tigerlake and beyond)
+ - PMC quirks as needed to enable SLPS0/S0ix
diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile
new file mode 100644
index 000000000000..8966fcdc0e1d
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Intel x86 Platform-Specific Drivers
+#
+
+intel_pmc_core-y := core.o
+obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
+intel_pmc_core_pltdrv-y := pltdrv.o
+obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
new file mode 100644
index 000000000000..ac19fcc9abbf
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -0,0 +1,2160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Core SoC Power Management Controller Driver
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
+ * Vishwanath Somayaji <vishwanath.somayaji@intel.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/uaccess.h>
+#include <linux/uuid.h>
+
+#include <acpi/acpi_bus.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+#include <asm/msr.h>
+#include <asm/tsc.h>
+
+#include "core.h"
+
+#define ACPI_S0IX_DSM_UUID "57a6512e-3979-4e9d-9708-ff13b2508972"
+#define ACPI_GET_LOW_MODE_REGISTERS 1
+
+/* PKGC MSRs are common across Intel Core SoCs */
+static const struct pmc_bit_map msr_map[] = {
+ {"Package C2", MSR_PKG_C2_RESIDENCY},
+ {"Package C3", MSR_PKG_C3_RESIDENCY},
+ {"Package C6", MSR_PKG_C6_RESIDENCY},
+ {"Package C7", MSR_PKG_C7_RESIDENCY},
+ {"Package C8", MSR_PKG_C8_RESIDENCY},
+ {"Package C9", MSR_PKG_C9_RESIDENCY},
+ {"Package C10", MSR_PKG_C10_RESIDENCY},
+ {}
+};
+
+static const struct pmc_bit_map spt_pll_map[] = {
+ {"MIPI PLL", SPT_PMC_BIT_MPHY_CMN_LANE0},
+ {"GEN2 USB2PCIE2 PLL", SPT_PMC_BIT_MPHY_CMN_LANE1},
+ {"DMIPCIE3 PLL", SPT_PMC_BIT_MPHY_CMN_LANE2},
+ {"SATA PLL", SPT_PMC_BIT_MPHY_CMN_LANE3},
+ {}
+};
+
+static const struct pmc_bit_map spt_mphy_map[] = {
+ {"MPHY CORE LANE 0", SPT_PMC_BIT_MPHY_LANE0},
+ {"MPHY CORE LANE 1", SPT_PMC_BIT_MPHY_LANE1},
+ {"MPHY CORE LANE 2", SPT_PMC_BIT_MPHY_LANE2},
+ {"MPHY CORE LANE 3", SPT_PMC_BIT_MPHY_LANE3},
+ {"MPHY CORE LANE 4", SPT_PMC_BIT_MPHY_LANE4},
+ {"MPHY CORE LANE 5", SPT_PMC_BIT_MPHY_LANE5},
+ {"MPHY CORE LANE 6", SPT_PMC_BIT_MPHY_LANE6},
+ {"MPHY CORE LANE 7", SPT_PMC_BIT_MPHY_LANE7},
+ {"MPHY CORE LANE 8", SPT_PMC_BIT_MPHY_LANE8},
+ {"MPHY CORE LANE 9", SPT_PMC_BIT_MPHY_LANE9},
+ {"MPHY CORE LANE 10", SPT_PMC_BIT_MPHY_LANE10},
+ {"MPHY CORE LANE 11", SPT_PMC_BIT_MPHY_LANE11},
+ {"MPHY CORE LANE 12", SPT_PMC_BIT_MPHY_LANE12},
+ {"MPHY CORE LANE 13", SPT_PMC_BIT_MPHY_LANE13},
+ {"MPHY CORE LANE 14", SPT_PMC_BIT_MPHY_LANE14},
+ {"MPHY CORE LANE 15", SPT_PMC_BIT_MPHY_LANE15},
+ {}
+};
+
+static const struct pmc_bit_map spt_pfear_map[] = {
+ {"PMC", SPT_PMC_BIT_PMC},
+ {"OPI-DMI", SPT_PMC_BIT_OPI},
+ {"SPI / eSPI", SPT_PMC_BIT_SPI},
+ {"XHCI", SPT_PMC_BIT_XHCI},
+ {"SPA", SPT_PMC_BIT_SPA},
+ {"SPB", SPT_PMC_BIT_SPB},
+ {"SPC", SPT_PMC_BIT_SPC},
+ {"GBE", SPT_PMC_BIT_GBE},
+ {"SATA", SPT_PMC_BIT_SATA},
+ {"HDA-PGD0", SPT_PMC_BIT_HDA_PGD0},
+ {"HDA-PGD1", SPT_PMC_BIT_HDA_PGD1},
+ {"HDA-PGD2", SPT_PMC_BIT_HDA_PGD2},
+ {"HDA-PGD3", SPT_PMC_BIT_HDA_PGD3},
+ {"RSVD", SPT_PMC_BIT_RSVD_0B},
+ {"LPSS", SPT_PMC_BIT_LPSS},
+ {"LPC", SPT_PMC_BIT_LPC},
+ {"SMB", SPT_PMC_BIT_SMB},
+ {"ISH", SPT_PMC_BIT_ISH},
+ {"P2SB", SPT_PMC_BIT_P2SB},
+ {"DFX", SPT_PMC_BIT_DFX},
+ {"SCC", SPT_PMC_BIT_SCC},
+ {"RSVD", SPT_PMC_BIT_RSVD_0C},
+ {"FUSE", SPT_PMC_BIT_FUSE},
+ {"CAMERA", SPT_PMC_BIT_CAMREA},
+ {"RSVD", SPT_PMC_BIT_RSVD_0D},
+ {"USB3-OTG", SPT_PMC_BIT_USB3_OTG},
+ {"EXI", SPT_PMC_BIT_EXI},
+ {"CSE", SPT_PMC_BIT_CSE},
+ {"CSME_KVM", SPT_PMC_BIT_CSME_KVM},
+ {"CSME_PMT", SPT_PMC_BIT_CSME_PMT},
+ {"CSME_CLINK", SPT_PMC_BIT_CSME_CLINK},
+ {"CSME_PTIO", SPT_PMC_BIT_CSME_PTIO},
+ {"CSME_USBR", SPT_PMC_BIT_CSME_USBR},
+ {"CSME_SUSRAM", SPT_PMC_BIT_CSME_SUSRAM},
+ {"CSME_SMT", SPT_PMC_BIT_CSME_SMT},
+ {"RSVD", SPT_PMC_BIT_RSVD_1A},
+ {"CSME_SMS2", SPT_PMC_BIT_CSME_SMS2},
+ {"CSME_SMS1", SPT_PMC_BIT_CSME_SMS1},
+ {"CSME_RTC", SPT_PMC_BIT_CSME_RTC},
+ {"CSME_PSF", SPT_PMC_BIT_CSME_PSF},
+ {}
+};
+
+static const struct pmc_bit_map *ext_spt_pfear_map[] = {
+ /*
+ * Check intel_pmc_core_ids[] users of spt_reg_map for
+ * a list of core SoCs using this.
+ */
+ spt_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map spt_ltr_show_map[] = {
+ {"SOUTHPORT_A", SPT_PMC_LTR_SPA},
+ {"SOUTHPORT_B", SPT_PMC_LTR_SPB},
+ {"SATA", SPT_PMC_LTR_SATA},
+ {"GIGABIT_ETHERNET", SPT_PMC_LTR_GBE},
+ {"XHCI", SPT_PMC_LTR_XHCI},
+ {"Reserved", SPT_PMC_LTR_RESERVED},
+ {"ME", SPT_PMC_LTR_ME},
+ /* EVA is Enterprise Value Add, doesn't really exist on PCH */
+ {"EVA", SPT_PMC_LTR_EVA},
+ {"SOUTHPORT_C", SPT_PMC_LTR_SPC},
+ {"HD_AUDIO", SPT_PMC_LTR_AZ},
+ {"LPSS", SPT_PMC_LTR_LPSS},
+ {"SOUTHPORT_D", SPT_PMC_LTR_SPD},
+ {"SOUTHPORT_E", SPT_PMC_LTR_SPE},
+ {"CAMERA", SPT_PMC_LTR_CAM},
+ {"ESPI", SPT_PMC_LTR_ESPI},
+ {"SCC", SPT_PMC_LTR_SCC},
+ {"ISH", SPT_PMC_LTR_ISH},
+ /* Below two cannot be used for LTR_IGNORE */
+ {"CURRENT_PLATFORM", SPT_PMC_LTR_CUR_PLT},
+ {"AGGREGATED_SYSTEM", SPT_PMC_LTR_CUR_ASLT},
+ {}
+};
+
+static const struct pmc_reg_map spt_reg_map = {
+ .pfear_sts = ext_spt_pfear_map,
+ .mphy_sts = spt_mphy_map,
+ .pll_sts = spt_pll_map,
+ .ltr_show_sts = spt_ltr_show_map,
+ .msr_sts = msr_map,
+ .slp_s0_offset = SPT_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP,
+ .ltr_ignore_offset = SPT_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = SPT_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = SPT_PMC_XRAM_PPFEAR0A,
+ .ppfear_buckets = SPT_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = SPT_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = SPT_PMC_READ_DISABLE_BIT,
+ .ltr_ignore_max = SPT_NUM_IP_IGN_ALLOWED,
+ .pm_vric1_offset = SPT_PMC_VRIC1_OFFSET,
+};
+
+/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */
+static const struct pmc_bit_map cnp_pfear_map[] = {
+ {"PMC", BIT(0)},
+ {"OPI-DMI", BIT(1)},
+ {"SPI/eSPI", BIT(2)},
+ {"XHCI", BIT(3)},
+ {"SPA", BIT(4)},
+ {"SPB", BIT(5)},
+ {"SPC", BIT(6)},
+ {"GBE", BIT(7)},
+
+ {"SATA", BIT(0)},
+ {"HDA_PGD0", BIT(1)},
+ {"HDA_PGD1", BIT(2)},
+ {"HDA_PGD2", BIT(3)},
+ {"HDA_PGD3", BIT(4)},
+ {"SPD", BIT(5)},
+ {"LPSS", BIT(6)},
+ {"LPC", BIT(7)},
+
+ {"SMB", BIT(0)},
+ {"ISH", BIT(1)},
+ {"P2SB", BIT(2)},
+ {"NPK_VNN", BIT(3)},
+ {"SDX", BIT(4)},
+ {"SPE", BIT(5)},
+ {"Fuse", BIT(6)},
+ {"SBR8", BIT(7)},
+
+ {"CSME_FSC", BIT(0)},
+ {"USB3_OTG", BIT(1)},
+ {"EXI", BIT(2)},
+ {"CSE", BIT(3)},
+ {"CSME_KVM", BIT(4)},
+ {"CSME_PMT", BIT(5)},
+ {"CSME_CLINK", BIT(6)},
+ {"CSME_PTIO", BIT(7)},
+
+ {"CSME_USBR", BIT(0)},
+ {"CSME_SUSRAM", BIT(1)},
+ {"CSME_SMT1", BIT(2)},
+ {"CSME_SMT4", BIT(3)},
+ {"CSME_SMS2", BIT(4)},
+ {"CSME_SMS1", BIT(5)},
+ {"CSME_RTC", BIT(6)},
+ {"CSME_PSF", BIT(7)},
+
+ {"SBR0", BIT(0)},
+ {"SBR1", BIT(1)},
+ {"SBR2", BIT(2)},
+ {"SBR3", BIT(3)},
+ {"SBR4", BIT(4)},
+ {"SBR5", BIT(5)},
+ {"CSME_PECI", BIT(6)},
+ {"PSF1", BIT(7)},
+
+ {"PSF2", BIT(0)},
+ {"PSF3", BIT(1)},
+ {"PSF4", BIT(2)},
+ {"CNVI", BIT(3)},
+ {"UFS0", BIT(4)},
+ {"EMMC", BIT(5)},
+ {"SPF", BIT(6)},
+ {"SBR6", BIT(7)},
+
+ {"SBR7", BIT(0)},
+ {"NPK_AON", BIT(1)},
+ {"HDA_PGD4", BIT(2)},
+ {"HDA_PGD5", BIT(3)},
+ {"HDA_PGD6", BIT(4)},
+ {"PSF6", BIT(5)},
+ {"PSF7", BIT(6)},
+ {"PSF8", BIT(7)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_cnp_pfear_map[] = {
+ /*
+ * Check intel_pmc_core_ids[] users of cnp_reg_map for
+ * a list of core SoCs using this.
+ */
+ cnp_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map icl_pfear_map[] = {
+ {"RES_65", BIT(0)},
+ {"RES_66", BIT(1)},
+ {"RES_67", BIT(2)},
+ {"TAM", BIT(3)},
+ {"GBETSN", BIT(4)},
+ {"TBTLSX", BIT(5)},
+ {"RES_71", BIT(6)},
+ {"RES_72", BIT(7)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_icl_pfear_map[] = {
+ /*
+ * Check intel_pmc_core_ids[] users of icl_reg_map for
+ * a list of core SoCs using this.
+ */
+ cnp_pfear_map,
+ icl_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map tgl_pfear_map[] = {
+ {"PSF9", BIT(0)},
+ {"RES_66", BIT(1)},
+ {"RES_67", BIT(2)},
+ {"RES_68", BIT(3)},
+ {"RES_69", BIT(4)},
+ {"RES_70", BIT(5)},
+ {"TBTLSX", BIT(6)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_tgl_pfear_map[] = {
+ /*
+ * Check intel_pmc_core_ids[] users of tgl_reg_map for
+ * a list of core SoCs using this.
+ */
+ cnp_pfear_map,
+ tgl_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map cnp_slps0_dbg0_map[] = {
+ {"AUDIO_D3", BIT(0)},
+ {"OTG_D3", BIT(1)},
+ {"XHCI_D3", BIT(2)},
+ {"LPIO_D3", BIT(3)},
+ {"SDX_D3", BIT(4)},
+ {"SATA_D3", BIT(5)},
+ {"UFS0_D3", BIT(6)},
+ {"UFS1_D3", BIT(7)},
+ {"EMMC_D3", BIT(8)},
+ {}
+};
+
+static const struct pmc_bit_map cnp_slps0_dbg1_map[] = {
+ {"SDIO_PLL_OFF", BIT(0)},
+ {"USB2_PLL_OFF", BIT(1)},
+ {"AUDIO_PLL_OFF", BIT(2)},
+ {"OC_PLL_OFF", BIT(3)},
+ {"MAIN_PLL_OFF", BIT(4)},
+ {"XOSC_OFF", BIT(5)},
+ {"LPC_CLKS_GATED", BIT(6)},
+ {"PCIE_CLKREQS_IDLE", BIT(7)},
+ {"AUDIO_ROSC_OFF", BIT(8)},
+ {"HPET_XOSC_CLK_REQ", BIT(9)},
+ {"PMC_ROSC_SLOW_CLK", BIT(10)},
+ {"AON2_ROSC_GATED", BIT(11)},
+ {"CLKACKS_DEASSERTED", BIT(12)},
+ {}
+};
+
+static const struct pmc_bit_map cnp_slps0_dbg2_map[] = {
+ {"MPHY_CORE_GATED", BIT(0)},
+ {"CSME_GATED", BIT(1)},
+ {"USB2_SUS_GATED", BIT(2)},
+ {"DYN_FLEX_IO_IDLE", BIT(3)},
+ {"GBE_NO_LINK", BIT(4)},
+ {"THERM_SEN_DISABLED", BIT(5)},
+ {"PCIE_LOW_POWER", BIT(6)},
+ {"ISH_VNNAON_REQ_ACT", BIT(7)},
+ {"ISH_VNN_REQ_ACT", BIT(8)},
+ {"CNV_VNNAON_REQ_ACT", BIT(9)},
+ {"CNV_VNN_REQ_ACT", BIT(10)},
+ {"NPK_VNNON_REQ_ACT", BIT(11)},
+ {"PMSYNC_STATE_IDLE", BIT(12)},
+ {"ALST_GT_THRES", BIT(13)},
+ {"PMC_ARC_PG_READY", BIT(14)},
+ {}
+};
+
+static const struct pmc_bit_map *cnp_slps0_dbg_maps[] = {
+ cnp_slps0_dbg0_map,
+ cnp_slps0_dbg1_map,
+ cnp_slps0_dbg2_map,
+ NULL
+};
+
+static const struct pmc_bit_map cnp_ltr_show_map[] = {
+ {"SOUTHPORT_A", CNP_PMC_LTR_SPA},
+ {"SOUTHPORT_B", CNP_PMC_LTR_SPB},
+ {"SATA", CNP_PMC_LTR_SATA},
+ {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE},
+ {"XHCI", CNP_PMC_LTR_XHCI},
+ {"Reserved", CNP_PMC_LTR_RESERVED},
+ {"ME", CNP_PMC_LTR_ME},
+ /* EVA is Enterprise Value Add, doesn't really exist on PCH */
+ {"EVA", CNP_PMC_LTR_EVA},
+ {"SOUTHPORT_C", CNP_PMC_LTR_SPC},
+ {"HD_AUDIO", CNP_PMC_LTR_AZ},
+ {"CNV", CNP_PMC_LTR_CNV},
+ {"LPSS", CNP_PMC_LTR_LPSS},
+ {"SOUTHPORT_D", CNP_PMC_LTR_SPD},
+ {"SOUTHPORT_E", CNP_PMC_LTR_SPE},
+ {"CAMERA", CNP_PMC_LTR_CAM},
+ {"ESPI", CNP_PMC_LTR_ESPI},
+ {"SCC", CNP_PMC_LTR_SCC},
+ {"ISH", CNP_PMC_LTR_ISH},
+ {"UFSX2", CNP_PMC_LTR_UFSX2},
+ {"EMMC", CNP_PMC_LTR_EMMC},
+ /*
+ * Check intel_pmc_core_ids[] users of cnp_reg_map for
+ * a list of core SoCs using this.
+ */
+ {"WIGIG", ICL_PMC_LTR_WIGIG},
+ {"THC0", TGL_PMC_LTR_THC0},
+ {"THC1", TGL_PMC_LTR_THC1},
+ /* Below two cannot be used for LTR_IGNORE */
+ {"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT},
+ {"AGGREGATED_SYSTEM", CNP_PMC_LTR_CUR_ASLT},
+ {}
+};
+
+static const struct pmc_reg_map cnp_reg_map = {
+ .pfear_sts = ext_cnp_pfear_map,
+ .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = SPT_PMC_SLP_S0_RES_COUNTER_STEP,
+ .slps0_dbg_maps = cnp_slps0_dbg_maps,
+ .ltr_show_sts = cnp_ltr_show_map,
+ .msr_sts = msr_map,
+ .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = CNP_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = CNP_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .ltr_ignore_max = CNP_NUM_IP_IGN_ALLOWED,
+ .etr3_offset = ETR3_OFFSET,
+};
+
+static const struct pmc_reg_map icl_reg_map = {
+ .pfear_sts = ext_icl_pfear_map,
+ .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = ICL_PMC_SLP_S0_RES_COUNTER_STEP,
+ .slps0_dbg_maps = cnp_slps0_dbg_maps,
+ .ltr_show_sts = cnp_ltr_show_map,
+ .msr_sts = msr_map,
+ .slps0_dbg_offset = CNP_PMC_SLPS0_DBG_OFFSET,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = CNP_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .ltr_ignore_max = ICL_NUM_IP_IGN_ALLOWED,
+ .etr3_offset = ETR3_OFFSET,
+};
+
+static const struct pmc_bit_map tgl_clocksource_status_map[] = {
+ {"USB2PLL_OFF_STS", BIT(18)},
+ {"PCIe/USB3.1_Gen2PLL_OFF_STS", BIT(19)},
+ {"PCIe_Gen3PLL_OFF_STS", BIT(20)},
+ {"OPIOPLL_OFF_STS", BIT(21)},
+ {"OCPLL_OFF_STS", BIT(22)},
+ {"MainPLL_OFF_STS", BIT(23)},
+ {"MIPIPLL_OFF_STS", BIT(24)},
+ {"Fast_XTAL_Osc_OFF_STS", BIT(25)},
+ {"AC_Ring_Osc_OFF_STS", BIT(26)},
+ {"MC_Ring_Osc_OFF_STS", BIT(27)},
+ {"SATAPLL_OFF_STS", BIT(29)},
+ {"XTAL_USB2PLL_OFF_STS", BIT(31)},
+ {}
+};
+
+static const struct pmc_bit_map tgl_power_gating_status_map[] = {
+ {"CSME_PG_STS", BIT(0)},
+ {"SATA_PG_STS", BIT(1)},
+ {"xHCI_PG_STS", BIT(2)},
+ {"UFSX2_PG_STS", BIT(3)},
+ {"OTG_PG_STS", BIT(5)},
+ {"SPA_PG_STS", BIT(6)},
+ {"SPB_PG_STS", BIT(7)},
+ {"SPC_PG_STS", BIT(8)},
+ {"SPD_PG_STS", BIT(9)},
+ {"SPE_PG_STS", BIT(10)},
+ {"SPF_PG_STS", BIT(11)},
+ {"LSX_PG_STS", BIT(13)},
+ {"P2SB_PG_STS", BIT(14)},
+ {"PSF_PG_STS", BIT(15)},
+ {"SBR_PG_STS", BIT(16)},
+ {"OPIDMI_PG_STS", BIT(17)},
+ {"THC0_PG_STS", BIT(18)},
+ {"THC1_PG_STS", BIT(19)},
+ {"GBETSN_PG_STS", BIT(20)},
+ {"GBE_PG_STS", BIT(21)},
+ {"LPSS_PG_STS", BIT(22)},
+ {"MMP_UFSX2_PG_STS", BIT(23)},
+ {"MMP_UFSX2B_PG_STS", BIT(24)},
+ {"FIA_PG_STS", BIT(25)},
+ {}
+};
+
+static const struct pmc_bit_map tgl_d3_status_map[] = {
+ {"ADSP_D3_STS", BIT(0)},
+ {"SATA_D3_STS", BIT(1)},
+ {"xHCI0_D3_STS", BIT(2)},
+ {"xDCI1_D3_STS", BIT(5)},
+ {"SDX_D3_STS", BIT(6)},
+ {"EMMC_D3_STS", BIT(7)},
+ {"IS_D3_STS", BIT(8)},
+ {"THC0_D3_STS", BIT(9)},
+ {"THC1_D3_STS", BIT(10)},
+ {"GBE_D3_STS", BIT(11)},
+ {"GBE_TSN_D3_STS", BIT(12)},
+ {}
+};
+
+static const struct pmc_bit_map tgl_vnn_req_status_map[] = {
+ {"GPIO_COM0_VNN_REQ_STS", BIT(1)},
+ {"GPIO_COM1_VNN_REQ_STS", BIT(2)},
+ {"GPIO_COM2_VNN_REQ_STS", BIT(3)},
+ {"GPIO_COM3_VNN_REQ_STS", BIT(4)},
+ {"GPIO_COM4_VNN_REQ_STS", BIT(5)},
+ {"GPIO_COM5_VNN_REQ_STS", BIT(6)},
+ {"Audio_VNN_REQ_STS", BIT(7)},
+ {"ISH_VNN_REQ_STS", BIT(8)},
+ {"CNVI_VNN_REQ_STS", BIT(9)},
+ {"eSPI_VNN_REQ_STS", BIT(10)},
+ {"Display_VNN_REQ_STS", BIT(11)},
+ {"DTS_VNN_REQ_STS", BIT(12)},
+ {"SMBUS_VNN_REQ_STS", BIT(14)},
+ {"CSME_VNN_REQ_STS", BIT(15)},
+ {"SMLINK0_VNN_REQ_STS", BIT(16)},
+ {"SMLINK1_VNN_REQ_STS", BIT(17)},
+ {"CLINK_VNN_REQ_STS", BIT(20)},
+ {"DCI_VNN_REQ_STS", BIT(21)},
+ {"ITH_VNN_REQ_STS", BIT(22)},
+ {"CSME_VNN_REQ_STS", BIT(24)},
+ {"GBE_VNN_REQ_STS", BIT(25)},
+ {}
+};
+
+static const struct pmc_bit_map tgl_vnn_misc_status_map[] = {
+ {"CPU_C10_REQ_STS_0", BIT(0)},
+ {"PCIe_LPM_En_REQ_STS_3", BIT(3)},
+ {"ITH_REQ_STS_5", BIT(5)},
+ {"CNVI_REQ_STS_6", BIT(6)},
+ {"ISH_REQ_STS_7", BIT(7)},
+ {"USB2_SUS_PG_Sys_REQ_STS_10", BIT(10)},
+ {"PCIe_Clk_REQ_STS_12", BIT(12)},
+ {"MPHY_Core_DL_REQ_STS_16", BIT(16)},
+ {"Break-even_En_REQ_STS_17", BIT(17)},
+ {"Auto-demo_En_REQ_STS_18", BIT(18)},
+ {"MPHY_SUS_REQ_STS_22", BIT(22)},
+ {"xDCI_attached_REQ_STS_24", BIT(24)},
+ {}
+};
+
+static const struct pmc_bit_map tgl_signal_status_map[] = {
+ {"LSX_Wake0_En_STS", BIT(0)},
+ {"LSX_Wake0_Pol_STS", BIT(1)},
+ {"LSX_Wake1_En_STS", BIT(2)},
+ {"LSX_Wake1_Pol_STS", BIT(3)},
+ {"LSX_Wake2_En_STS", BIT(4)},
+ {"LSX_Wake2_Pol_STS", BIT(5)},
+ {"LSX_Wake3_En_STS", BIT(6)},
+ {"LSX_Wake3_Pol_STS", BIT(7)},
+ {"LSX_Wake4_En_STS", BIT(8)},
+ {"LSX_Wake4_Pol_STS", BIT(9)},
+ {"LSX_Wake5_En_STS", BIT(10)},
+ {"LSX_Wake5_Pol_STS", BIT(11)},
+ {"LSX_Wake6_En_STS", BIT(12)},
+ {"LSX_Wake6_Pol_STS", BIT(13)},
+ {"LSX_Wake7_En_STS", BIT(14)},
+ {"LSX_Wake7_Pol_STS", BIT(15)},
+ {"Intel_Se_IO_Wake0_En_STS", BIT(16)},
+ {"Intel_Se_IO_Wake0_Pol_STS", BIT(17)},
+ {"Intel_Se_IO_Wake1_En_STS", BIT(18)},
+ {"Intel_Se_IO_Wake1_Pol_STS", BIT(19)},
+ {"Int_Timer_SS_Wake0_En_STS", BIT(20)},
+ {"Int_Timer_SS_Wake0_Pol_STS", BIT(21)},
+ {"Int_Timer_SS_Wake1_En_STS", BIT(22)},
+ {"Int_Timer_SS_Wake1_Pol_STS", BIT(23)},
+ {"Int_Timer_SS_Wake2_En_STS", BIT(24)},
+ {"Int_Timer_SS_Wake2_Pol_STS", BIT(25)},
+ {"Int_Timer_SS_Wake3_En_STS", BIT(26)},
+ {"Int_Timer_SS_Wake3_Pol_STS", BIT(27)},
+ {"Int_Timer_SS_Wake4_En_STS", BIT(28)},
+ {"Int_Timer_SS_Wake4_Pol_STS", BIT(29)},
+ {"Int_Timer_SS_Wake5_En_STS", BIT(30)},
+ {"Int_Timer_SS_Wake5_Pol_STS", BIT(31)},
+ {}
+};
+
+static const struct pmc_bit_map *tgl_lpm_maps[] = {
+ tgl_clocksource_status_map,
+ tgl_power_gating_status_map,
+ tgl_d3_status_map,
+ tgl_vnn_req_status_map,
+ tgl_vnn_misc_status_map,
+ tgl_signal_status_map,
+ NULL
+};
+
+static const struct pmc_reg_map tgl_reg_map = {
+ .pfear_sts = ext_tgl_pfear_map,
+ .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+ .ltr_show_sts = cnp_ltr_show_map,
+ .msr_sts = msr_map,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = CNP_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = ICL_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED,
+ .lpm_num_maps = TGL_LPM_NUM_MAPS,
+ .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+ .lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET,
+ .lpm_en_offset = TGL_LPM_EN_OFFSET,
+ .lpm_priority_offset = TGL_LPM_PRI_OFFSET,
+ .lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET,
+ .lpm_sts = tgl_lpm_maps,
+ .lpm_status_offset = TGL_LPM_STATUS_OFFSET,
+ .lpm_live_status_offset = TGL_LPM_LIVE_STATUS_OFFSET,
+ .etr3_offset = ETR3_OFFSET,
+};
+
+static void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev)
+{
+ struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
+ const int num_maps = pmcdev->map->lpm_num_maps;
+ u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4;
+ union acpi_object *out_obj;
+ struct acpi_device *adev;
+ guid_t s0ix_dsm_guid;
+ u32 *lpm_req_regs, *addr;
+
+ adev = ACPI_COMPANION(&pdev->dev);
+ if (!adev)
+ return;
+
+ guid_parse(ACPI_S0IX_DSM_UUID, &s0ix_dsm_guid);
+
+ out_obj = acpi_evaluate_dsm(adev->handle, &s0ix_dsm_guid, 0,
+ ACPI_GET_LOW_MODE_REGISTERS, NULL);
+ if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) {
+ u32 size = out_obj->buffer.length;
+
+ if (size != lpm_size) {
+ acpi_handle_debug(adev->handle,
+ "_DSM returned unexpected buffer size, have %u, expect %u\n",
+ size, lpm_size);
+ goto free_acpi_obj;
+ }
+ } else {
+ acpi_handle_debug(adev->handle,
+ "_DSM function 0 evaluation failed\n");
+ goto free_acpi_obj;
+ }
+
+ addr = (u32 *)out_obj->buffer.pointer;
+
+ lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32),
+ GFP_KERNEL);
+ if (!lpm_req_regs)
+ goto free_acpi_obj;
+
+ memcpy(lpm_req_regs, addr, lpm_size);
+ pmcdev->lpm_req_regs = lpm_req_regs;
+
+free_acpi_obj:
+ ACPI_FREE(out_obj);
+}
+
+/* Alder Lake: PGD PFET Enable Ack Status Register(s) bitmap */
+static const struct pmc_bit_map adl_pfear_map[] = {
+ {"SPI/eSPI", BIT(2)},
+ {"XHCI", BIT(3)},
+ {"SPA", BIT(4)},
+ {"SPB", BIT(5)},
+ {"SPC", BIT(6)},
+ {"GBE", BIT(7)},
+
+ {"SATA", BIT(0)},
+ {"HDA_PGD0", BIT(1)},
+ {"HDA_PGD1", BIT(2)},
+ {"HDA_PGD2", BIT(3)},
+ {"HDA_PGD3", BIT(4)},
+ {"SPD", BIT(5)},
+ {"LPSS", BIT(6)},
+
+ {"SMB", BIT(0)},
+ {"ISH", BIT(1)},
+ {"ITH", BIT(3)},
+
+ {"XDCI", BIT(1)},
+ {"DCI", BIT(2)},
+ {"CSE", BIT(3)},
+ {"CSME_KVM", BIT(4)},
+ {"CSME_PMT", BIT(5)},
+ {"CSME_CLINK", BIT(6)},
+ {"CSME_PTIO", BIT(7)},
+
+ {"CSME_USBR", BIT(0)},
+ {"CSME_SUSRAM", BIT(1)},
+ {"CSME_SMT1", BIT(2)},
+ {"CSME_SMS2", BIT(4)},
+ {"CSME_SMS1", BIT(5)},
+ {"CSME_RTC", BIT(6)},
+ {"CSME_PSF", BIT(7)},
+
+ {"CNVI", BIT(3)},
+
+ {"HDA_PGD4", BIT(2)},
+ {"HDA_PGD5", BIT(3)},
+ {"HDA_PGD6", BIT(4)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_adl_pfear_map[] = {
+ /*
+ * Check intel_pmc_core_ids[] users of cnp_reg_map for
+ * a list of core SoCs using this.
+ */
+ adl_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map adl_ltr_show_map[] = {
+ {"SOUTHPORT_A", CNP_PMC_LTR_SPA},
+ {"SOUTHPORT_B", CNP_PMC_LTR_SPB},
+ {"SATA", CNP_PMC_LTR_SATA},
+ {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE},
+ {"XHCI", CNP_PMC_LTR_XHCI},
+ {"SOUTHPORT_F", ADL_PMC_LTR_SPF},
+ {"ME", CNP_PMC_LTR_ME},
+ /* EVA is Enterprise Value Add, doesn't really exist on PCH */
+ {"SATA1", CNP_PMC_LTR_EVA},
+ {"SOUTHPORT_C", CNP_PMC_LTR_SPC},
+ {"HD_AUDIO", CNP_PMC_LTR_AZ},
+ {"CNV", CNP_PMC_LTR_CNV},
+ {"LPSS", CNP_PMC_LTR_LPSS},
+ {"SOUTHPORT_D", CNP_PMC_LTR_SPD},
+ {"SOUTHPORT_E", CNP_PMC_LTR_SPE},
+ {"SATA2", CNP_PMC_LTR_CAM},
+ {"ESPI", CNP_PMC_LTR_ESPI},
+ {"SCC", CNP_PMC_LTR_SCC},
+ {"ISH", CNP_PMC_LTR_ISH},
+ {"UFSX2", CNP_PMC_LTR_UFSX2},
+ {"EMMC", CNP_PMC_LTR_EMMC},
+ /*
+ * Check intel_pmc_core_ids[] users of cnp_reg_map for
+ * a list of core SoCs using this.
+ */
+ {"WIGIG", ICL_PMC_LTR_WIGIG},
+ {"THC0", TGL_PMC_LTR_THC0},
+ {"THC1", TGL_PMC_LTR_THC1},
+ {"SOUTHPORT_G", CNP_PMC_LTR_RESERVED},
+
+ /* Below two cannot be used for LTR_IGNORE */
+ {"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT},
+ {"AGGREGATED_SYSTEM", CNP_PMC_LTR_CUR_ASLT},
+ {}
+};
+
+static const struct pmc_bit_map adl_clocksource_status_map[] = {
+ {"CLKPART1_OFF_STS", BIT(0)},
+ {"CLKPART2_OFF_STS", BIT(1)},
+ {"CLKPART3_OFF_STS", BIT(2)},
+ {"CLKPART4_OFF_STS", BIT(3)},
+ {"CLKPART5_OFF_STS", BIT(4)},
+ {"CLKPART6_OFF_STS", BIT(5)},
+ {"CLKPART7_OFF_STS", BIT(6)},
+ {"CLKPART8_OFF_STS", BIT(7)},
+ {"PCIE0PLL_OFF_STS", BIT(10)},
+ {"PCIE1PLL_OFF_STS", BIT(11)},
+ {"PCIE2PLL_OFF_STS", BIT(12)},
+ {"PCIE3PLL_OFF_STS", BIT(13)},
+ {"PCIE4PLL_OFF_STS", BIT(14)},
+ {"PCIE5PLL_OFF_STS", BIT(15)},
+ {"PCIE6PLL_OFF_STS", BIT(16)},
+ {"USB2PLL_OFF_STS", BIT(18)},
+ {"OCPLL_OFF_STS", BIT(22)},
+ {"AUDIOPLL_OFF_STS", BIT(23)},
+ {"GBEPLL_OFF_STS", BIT(24)},
+ {"Fast_XTAL_Osc_OFF_STS", BIT(25)},
+ {"AC_Ring_Osc_OFF_STS", BIT(26)},
+ {"MC_Ring_Osc_OFF_STS", BIT(27)},
+ {"SATAPLL_OFF_STS", BIT(29)},
+ {"USB3PLL_OFF_STS", BIT(31)},
+ {}
+};
+
+static const struct pmc_bit_map adl_power_gating_status_0_map[] = {
+ {"PMC_PGD0_PG_STS", BIT(0)},
+ {"DMI_PGD0_PG_STS", BIT(1)},
+ {"ESPISPI_PGD0_PG_STS", BIT(2)},
+ {"XHCI_PGD0_PG_STS", BIT(3)},
+ {"SPA_PGD0_PG_STS", BIT(4)},
+ {"SPB_PGD0_PG_STS", BIT(5)},
+ {"SPC_PGD0_PG_STS", BIT(6)},
+ {"GBE_PGD0_PG_STS", BIT(7)},
+ {"SATA_PGD0_PG_STS", BIT(8)},
+ {"DSP_PGD0_PG_STS", BIT(9)},
+ {"DSP_PGD1_PG_STS", BIT(10)},
+ {"DSP_PGD2_PG_STS", BIT(11)},
+ {"DSP_PGD3_PG_STS", BIT(12)},
+ {"SPD_PGD0_PG_STS", BIT(13)},
+ {"LPSS_PGD0_PG_STS", BIT(14)},
+ {"SMB_PGD0_PG_STS", BIT(16)},
+ {"ISH_PGD0_PG_STS", BIT(17)},
+ {"NPK_PGD0_PG_STS", BIT(19)},
+ {"PECI_PGD0_PG_STS", BIT(21)},
+ {"XDCI_PGD0_PG_STS", BIT(25)},
+ {"EXI_PGD0_PG_STS", BIT(26)},
+ {"CSE_PGD0_PG_STS", BIT(27)},
+ {"KVMCC_PGD0_PG_STS", BIT(28)},
+ {"PMT_PGD0_PG_STS", BIT(29)},
+ {"CLINK_PGD0_PG_STS", BIT(30)},
+ {"PTIO_PGD0_PG_STS", BIT(31)},
+ {}
+};
+
+static const struct pmc_bit_map adl_power_gating_status_1_map[] = {
+ {"USBR0_PGD0_PG_STS", BIT(0)},
+ {"SMT1_PGD0_PG_STS", BIT(2)},
+ {"CSMERTC_PGD0_PG_STS", BIT(6)},
+ {"CSMEPSF_PGD0_PG_STS", BIT(7)},
+ {"CNVI_PGD0_PG_STS", BIT(19)},
+ {"DSP_PGD4_PG_STS", BIT(26)},
+ {"SPG_PGD0_PG_STS", BIT(27)},
+ {"SPE_PGD0_PG_STS", BIT(28)},
+ {}
+};
+
+static const struct pmc_bit_map adl_power_gating_status_2_map[] = {
+ {"THC0_PGD0_PG_STS", BIT(7)},
+ {"THC1_PGD0_PG_STS", BIT(8)},
+ {"SPF_PGD0_PG_STS", BIT(14)},
+ {}
+};
+
+static const struct pmc_bit_map adl_d3_status_0_map[] = {
+ {"ISH_D3_STS", BIT(2)},
+ {"LPSS_D3_STS", BIT(3)},
+ {"XDCI_D3_STS", BIT(4)},
+ {"XHCI_D3_STS", BIT(5)},
+ {"SPA_D3_STS", BIT(12)},
+ {"SPB_D3_STS", BIT(13)},
+ {"SPC_D3_STS", BIT(14)},
+ {"SPD_D3_STS", BIT(15)},
+ {"SPE_D3_STS", BIT(16)},
+ {"DSP_D3_STS", BIT(19)},
+ {"SATA_D3_STS", BIT(20)},
+ {"DMI_D3_STS", BIT(22)},
+ {}
+};
+
+static const struct pmc_bit_map adl_d3_status_1_map[] = {
+ {"GBE_D3_STS", BIT(19)},
+ {"CNVI_D3_STS", BIT(27)},
+ {}
+};
+
+static const struct pmc_bit_map adl_d3_status_2_map[] = {
+ {"CSMERTC_D3_STS", BIT(1)},
+ {"CSE_D3_STS", BIT(4)},
+ {"KVMCC_D3_STS", BIT(5)},
+ {"USBR0_D3_STS", BIT(6)},
+ {"SMT1_D3_STS", BIT(8)},
+ {"PTIO_D3_STS", BIT(16)},
+ {"PMT_D3_STS", BIT(17)},
+ {}
+};
+
+static const struct pmc_bit_map adl_d3_status_3_map[] = {
+ {"THC0_D3_STS", BIT(14)},
+ {"THC1_D3_STS", BIT(15)},
+ {}
+};
+
+static const struct pmc_bit_map adl_vnn_req_status_0_map[] = {
+ {"ISH_VNN_REQ_STS", BIT(2)},
+ {"ESPISPI_VNN_REQ_STS", BIT(18)},
+ {"DSP_VNN_REQ_STS", BIT(19)},
+ {}
+};
+
+static const struct pmc_bit_map adl_vnn_req_status_1_map[] = {
+ {"NPK_VNN_REQ_STS", BIT(4)},
+ {"EXI_VNN_REQ_STS", BIT(9)},
+ {"GBE_VNN_REQ_STS", BIT(19)},
+ {"SMB_VNN_REQ_STS", BIT(25)},
+ {"CNVI_VNN_REQ_STS", BIT(27)},
+ {}
+};
+
+static const struct pmc_bit_map adl_vnn_req_status_2_map[] = {
+ {"CSMERTC_VNN_REQ_STS", BIT(1)},
+ {"CSE_VNN_REQ_STS", BIT(4)},
+ {"SMT1_VNN_REQ_STS", BIT(8)},
+ {"CLINK_VNN_REQ_STS", BIT(14)},
+ {"GPIOCOM4_VNN_REQ_STS", BIT(20)},
+ {"GPIOCOM3_VNN_REQ_STS", BIT(21)},
+ {"GPIOCOM2_VNN_REQ_STS", BIT(22)},
+ {"GPIOCOM1_VNN_REQ_STS", BIT(23)},
+ {"GPIOCOM0_VNN_REQ_STS", BIT(24)},
+ {}
+};
+
+static const struct pmc_bit_map adl_vnn_req_status_3_map[] = {
+ {"GPIOCOM5_VNN_REQ_STS", BIT(11)},
+ {}
+};
+
+static const struct pmc_bit_map adl_vnn_misc_status_map[] = {
+ {"CPU_C10_REQ_STS", BIT(0)},
+ {"PCIe_LPM_En_REQ_STS", BIT(3)},
+ {"ITH_REQ_STS", BIT(5)},
+ {"CNVI_REQ_STS", BIT(6)},
+ {"ISH_REQ_STS", BIT(7)},
+ {"USB2_SUS_PG_Sys_REQ_STS", BIT(10)},
+ {"PCIe_Clk_REQ_STS", BIT(12)},
+ {"MPHY_Core_DL_REQ_STS", BIT(16)},
+ {"Break-even_En_REQ_STS", BIT(17)},
+ {"MPHY_SUS_REQ_STS", BIT(22)},
+ {"xDCI_attached_REQ_STS", BIT(24)},
+ {}
+};
+
+static const struct pmc_bit_map *adl_lpm_maps[] = {
+ adl_clocksource_status_map,
+ adl_power_gating_status_0_map,
+ adl_power_gating_status_1_map,
+ adl_power_gating_status_2_map,
+ adl_d3_status_0_map,
+ adl_d3_status_1_map,
+ adl_d3_status_2_map,
+ adl_d3_status_3_map,
+ adl_vnn_req_status_0_map,
+ adl_vnn_req_status_1_map,
+ adl_vnn_req_status_2_map,
+ adl_vnn_req_status_3_map,
+ adl_vnn_misc_status_map,
+ tgl_signal_status_map,
+ NULL
+};
+
+static const struct pmc_reg_map adl_reg_map = {
+ .pfear_sts = ext_adl_pfear_map,
+ .slp_s0_offset = ADL_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+ .ltr_show_sts = adl_ltr_show_map,
+ .msr_sts = msr_map,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = CNP_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = CNP_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .ltr_ignore_max = ADL_NUM_IP_IGN_ALLOWED,
+ .lpm_num_modes = ADL_LPM_NUM_MODES,
+ .lpm_num_maps = ADL_LPM_NUM_MAPS,
+ .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+ .etr3_offset = ETR3_OFFSET,
+ .lpm_sts_latch_en_offset = ADL_LPM_STATUS_LATCH_EN_OFFSET,
+ .lpm_priority_offset = ADL_LPM_PRI_OFFSET,
+ .lpm_en_offset = ADL_LPM_EN_OFFSET,
+ .lpm_residency_offset = ADL_LPM_RESIDENCY_OFFSET,
+ .lpm_sts = adl_lpm_maps,
+ .lpm_status_offset = ADL_LPM_STATUS_OFFSET,
+ .lpm_live_status_offset = ADL_LPM_LIVE_STATUS_OFFSET,
+};
+
+static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)
+{
+ return readl(pmcdev->regbase + reg_offset);
+}
+
+static inline void pmc_core_reg_write(struct pmc_dev *pmcdev, int reg_offset,
+ u32 val)
+{
+ writel(val, pmcdev->regbase + reg_offset);
+}
+
+static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value)
+{
+ return (u64)value * pmcdev->map->slp_s0_res_counter_step;
+}
+
+static int set_etr3(struct pmc_dev *pmcdev)
+{
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+ int err;
+
+ if (!map->etr3_offset)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&pmcdev->lock);
+
+ /* check if CF9 is locked */
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ if (reg & ETR3_CF9LOCK) {
+ err = -EACCES;
+ goto out_unlock;
+ }
+
+ /* write CF9 global reset bit */
+ reg |= ETR3_CF9GR;
+ pmc_core_reg_write(pmcdev, map->etr3_offset, reg);
+
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ if (!(reg & ETR3_CF9GR)) {
+ err = -EIO;
+ goto out_unlock;
+ }
+
+ err = 0;
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+ return err;
+}
+static umode_t etr3_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+
+ mutex_lock(&pmcdev->lock);
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ mutex_unlock(&pmcdev->lock);
+
+ return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode;
+}
+
+static ssize_t etr3_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+
+ if (!map->etr3_offset)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ reg &= ETR3_CF9GR | ETR3_CF9LOCK;
+
+ mutex_unlock(&pmcdev->lock);
+
+ return sysfs_emit(buf, "0x%08x", reg);
+}
+
+static ssize_t etr3_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ int err;
+ u32 reg;
+
+ err = kstrtouint(buf, 16, &reg);
+ if (err)
+ return err;
+
+ /* allow only CF9 writes */
+ if (reg != ETR3_CF9GR)
+ return -EINVAL;
+
+ err = set_etr3(pmcdev);
+ if (err)
+ return err;
+
+ return len;
+}
+static DEVICE_ATTR_RW(etr3);
+
+static struct attribute *pmc_attrs[] = {
+ &dev_attr_etr3.attr,
+ NULL
+};
+
+static const struct attribute_group pmc_attr_group = {
+ .attrs = pmc_attrs,
+ .is_visible = etr3_is_visible,
+};
+
+static const struct attribute_group *pmc_dev_groups[] = {
+ &pmc_attr_group,
+ NULL
+};
+
+static int pmc_core_dev_state_get(void *data, u64 *val)
+{
+ struct pmc_dev *pmcdev = data;
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 value;
+
+ value = pmc_core_reg_read(pmcdev, map->slp_s0_offset);
+ *val = pmc_core_adjust_slp_s0_step(pmcdev, value);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n");
+
+static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev)
+{
+ u32 value;
+
+ value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset);
+ return value & BIT(pmcdev->map->pm_read_disable_bit);
+}
+
+static void pmc_core_slps0_display(struct pmc_dev *pmcdev, struct device *dev,
+ struct seq_file *s)
+{
+ const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps;
+ const struct pmc_bit_map *map;
+ int offset = pmcdev->map->slps0_dbg_offset;
+ u32 data;
+
+ while (*maps) {
+ map = *maps;
+ data = pmc_core_reg_read(pmcdev, offset);
+ offset += 4;
+ while (map->name) {
+ if (dev)
+ dev_info(dev, "SLP_S0_DBG: %-32s\tState: %s\n",
+ map->name,
+ data & map->bit_mask ? "Yes" : "No");
+ if (s)
+ seq_printf(s, "SLP_S0_DBG: %-32s\tState: %s\n",
+ map->name,
+ data & map->bit_mask ? "Yes" : "No");
+ ++map;
+ }
+ ++maps;
+ }
+}
+
+static int pmc_core_lpm_get_arr_size(const struct pmc_bit_map **maps)
+{
+ int idx;
+
+ for (idx = 0; maps[idx]; idx++)
+ ;/* Nothing */
+
+ return idx;
+}
+
+static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev,
+ struct seq_file *s, u32 offset,
+ const char *str,
+ const struct pmc_bit_map **maps)
+{
+ int index, idx, len = 32, bit_mask, arr_size;
+ u32 *lpm_regs;
+
+ arr_size = pmc_core_lpm_get_arr_size(maps);
+ lpm_regs = kmalloc_array(arr_size, sizeof(*lpm_regs), GFP_KERNEL);
+ if (!lpm_regs)
+ return;
+
+ for (index = 0; index < arr_size; index++) {
+ lpm_regs[index] = pmc_core_reg_read(pmcdev, offset);
+ offset += 4;
+ }
+
+ for (idx = 0; idx < arr_size; idx++) {
+ if (dev)
+ dev_info(dev, "\nLPM_%s_%d:\t0x%x\n", str, idx,
+ lpm_regs[idx]);
+ if (s)
+ seq_printf(s, "\nLPM_%s_%d:\t0x%x\n", str, idx,
+ lpm_regs[idx]);
+ for (index = 0; maps[idx][index].name && index < len; index++) {
+ bit_mask = maps[idx][index].bit_mask;
+ if (dev)
+ dev_info(dev, "%-30s %-30d\n",
+ maps[idx][index].name,
+ lpm_regs[idx] & bit_mask ? 1 : 0);
+ if (s)
+ seq_printf(s, "%-30s %-30d\n",
+ maps[idx][index].name,
+ lpm_regs[idx] & bit_mask ? 1 : 0);
+ }
+ }
+
+ kfree(lpm_regs);
+}
+
+static bool slps0_dbg_latch;
+
+static inline u8 pmc_core_reg_read_byte(struct pmc_dev *pmcdev, int offset)
+{
+ return readb(pmcdev->regbase + offset);
+}
+
+static void pmc_core_display_map(struct seq_file *s, int index, int idx, int ip,
+ u8 pf_reg, const struct pmc_bit_map **pf_map)
+{
+ seq_printf(s, "PCH IP: %-2d - %-32s\tState: %s\n",
+ ip, pf_map[idx][index].name,
+ pf_map[idx][index].bit_mask & pf_reg ? "Off" : "On");
+}
+
+static int pmc_core_ppfear_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map **maps = pmcdev->map->pfear_sts;
+ u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES];
+ int index, iter, idx, ip = 0;
+
+ iter = pmcdev->map->ppfear0_offset;
+
+ for (index = 0; index < pmcdev->map->ppfear_buckets &&
+ index < PPFEAR_MAX_NUM_ENTRIES; index++, iter++)
+ pf_regs[index] = pmc_core_reg_read_byte(pmcdev, iter);
+
+ for (idx = 0; maps[idx]; idx++) {
+ for (index = 0; maps[idx][index].name &&
+ index < pmcdev->map->ppfear_buckets * 8; ip++, index++)
+ pmc_core_display_map(s, index, idx, ip,
+ pf_regs[index / 8], maps);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_ppfear);
+
+/* This function should return link status, 0 means ready */
+static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev)
+{
+ u32 value;
+
+ value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET);
+ return value & BIT(SPT_PMC_MSG_FULL_STS_BIT);
+}
+
+static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram)
+{
+ u32 dest;
+ int timeout;
+
+ for (timeout = NUM_RETRIES; timeout > 0; timeout--) {
+ if (pmc_core_mtpmc_link_status(pmcdev) == 0)
+ break;
+ msleep(5);
+ }
+
+ if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev))
+ return -EBUSY;
+
+ dest = (*addr_xram & MTPMC_MASK) | (1U << 1);
+ pmc_core_reg_write(pmcdev, SPT_PMC_MTPMC_OFFSET, dest);
+ return 0;
+}
+
+static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map *map = pmcdev->map->mphy_sts;
+ u32 mphy_core_reg_low, mphy_core_reg_high;
+ u32 val_low, val_high;
+ int index, err = 0;
+
+ if (pmcdev->pmc_xram_read_bit) {
+ seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS.");
+ return 0;
+ }
+
+ mphy_core_reg_low = (SPT_PMC_MPHY_CORE_STS_0 << 16);
+ mphy_core_reg_high = (SPT_PMC_MPHY_CORE_STS_1 << 16);
+
+ mutex_lock(&pmcdev->lock);
+
+ if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ msleep(10);
+ val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
+
+ if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ msleep(10);
+ val_high = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
+
+ for (index = 0; index < 8 && map[index].name; index++) {
+ seq_printf(s, "%-32s\tState: %s\n",
+ map[index].name,
+ map[index].bit_mask & val_low ? "Not power gated" :
+ "Power gated");
+ }
+
+ for (index = 8; map[index].name; index++) {
+ seq_printf(s, "%-32s\tState: %s\n",
+ map[index].name,
+ map[index].bit_mask & val_high ? "Not power gated" :
+ "Power gated");
+ }
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+ return err;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_mphy_pg);
+
+static int pmc_core_pll_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map *map = pmcdev->map->pll_sts;
+ u32 mphy_common_reg, val;
+ int index, err = 0;
+
+ if (pmcdev->pmc_xram_read_bit) {
+ seq_puts(s, "Access denied: please disable PMC_READ_DISABLE setting in BIOS.");
+ return 0;
+ }
+
+ mphy_common_reg = (SPT_PMC_MPHY_COM_STS_0 << 16);
+ mutex_lock(&pmcdev->lock);
+
+ if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+
+ /* Observed PMC HW response latency for MTPMC-MFPMC is ~10 ms */
+ msleep(10);
+ val = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
+
+ for (index = 0; map[index].name ; index++) {
+ seq_printf(s, "%-32s\tState: %s\n",
+ map[index].name,
+ map[index].bit_mask & val ? "Active" : "Idle");
+ }
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+ return err;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_pll);
+
+static int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value)
+{
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+ int err = 0;
+
+ mutex_lock(&pmcdev->lock);
+
+ if (value > map->ltr_ignore_max) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset);
+ reg |= BIT(value);
+ pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg);
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+
+ return err;
+}
+
+static ssize_t pmc_core_ltr_ignore_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct pmc_dev *pmcdev = s->private;
+ u32 buf_size, value;
+ int err;
+
+ buf_size = min_t(u32, count, 64);
+
+ err = kstrtou32_from_user(userbuf, buf_size, 10, &value);
+ if (err)
+ return err;
+
+ err = pmc_core_send_ltr_ignore(pmcdev, value);
+
+ return err == 0 ? count : err;
+}
+
+static int pmc_core_ltr_ignore_show(struct seq_file *s, void *unused)
+{
+ return 0;
+}
+
+static int pmc_core_ltr_ignore_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pmc_core_ltr_ignore_show, inode->i_private);
+}
+
+static const struct file_operations pmc_core_ltr_ignore_ops = {
+ .open = pmc_core_ltr_ignore_open,
+ .read = seq_read,
+ .write = pmc_core_ltr_ignore_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
+{
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 fd;
+
+ mutex_lock(&pmcdev->lock);
+
+ if (!reset && !slps0_dbg_latch)
+ goto out_unlock;
+
+ fd = pmc_core_reg_read(pmcdev, map->slps0_dbg_offset);
+ if (reset)
+ fd &= ~CNP_PMC_LATCH_SLPS0_EVENTS;
+ else
+ fd |= CNP_PMC_LATCH_SLPS0_EVENTS;
+ pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd);
+
+ slps0_dbg_latch = false;
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+}
+
+static int pmc_core_slps0_dbg_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+
+ pmc_core_slps0_dbg_latch(pmcdev, false);
+ pmc_core_slps0_display(pmcdev, NULL, s);
+ pmc_core_slps0_dbg_latch(pmcdev, true);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_slps0_dbg);
+
+static u32 convert_ltr_scale(u32 val)
+{
+ /*
+ * As per PCIE specification supporting document
+ * ECN_LatencyTolnReporting_14Aug08.pdf the Latency
+ * Tolerance Reporting data payload is encoded in a
+ * 3 bit scale and 10 bit value fields. Values are
+ * multiplied by the indicated scale to yield an absolute time
+ * value, expressible in a range from 1 nanosecond to
+ * 2^25*(2^10-1) = 34,326,183,936 nanoseconds.
+ *
+ * scale encoding is as follows:
+ *
+ * ----------------------------------------------
+ * |scale factor | Multiplier (ns) |
+ * ----------------------------------------------
+ * | 0 | 1 |
+ * | 1 | 32 |
+ * | 2 | 1024 |
+ * | 3 | 32768 |
+ * | 4 | 1048576 |
+ * | 5 | 33554432 |
+ * | 6 | Invalid |
+ * | 7 | Invalid |
+ * ----------------------------------------------
+ */
+ if (val > 5) {
+ pr_warn("Invalid LTR scale factor.\n");
+ return 0;
+ }
+
+ return 1U << (5 * val);
+}
+
+static int pmc_core_ltr_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map *map = pmcdev->map->ltr_show_sts;
+ u64 decoded_snoop_ltr, decoded_non_snoop_ltr;
+ u32 ltr_raw_data, scale, val;
+ u16 snoop_ltr, nonsnoop_ltr;
+ int index;
+
+ for (index = 0; map[index].name ; index++) {
+ decoded_snoop_ltr = decoded_non_snoop_ltr = 0;
+ ltr_raw_data = pmc_core_reg_read(pmcdev,
+ map[index].bit_mask);
+ snoop_ltr = ltr_raw_data & ~MTPMC_MASK;
+ nonsnoop_ltr = (ltr_raw_data >> 0x10) & ~MTPMC_MASK;
+
+ if (FIELD_GET(LTR_REQ_NONSNOOP, ltr_raw_data)) {
+ scale = FIELD_GET(LTR_DECODED_SCALE, nonsnoop_ltr);
+ val = FIELD_GET(LTR_DECODED_VAL, nonsnoop_ltr);
+ decoded_non_snoop_ltr = val * convert_ltr_scale(scale);
+ }
+
+ if (FIELD_GET(LTR_REQ_SNOOP, ltr_raw_data)) {
+ scale = FIELD_GET(LTR_DECODED_SCALE, snoop_ltr);
+ val = FIELD_GET(LTR_DECODED_VAL, snoop_ltr);
+ decoded_snoop_ltr = val * convert_ltr_scale(scale);
+ }
+
+ seq_printf(s, "%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\n",
+ map[index].name, ltr_raw_data,
+ decoded_non_snoop_ltr,
+ decoded_snoop_ltr);
+ }
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr);
+
+static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset,
+ const int lpm_adj_x2)
+{
+ u64 lpm_res = pmc_core_reg_read(pmcdev, offset);
+
+ return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res);
+}
+
+static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2;
+ u32 offset = pmcdev->map->lpm_residency_offset;
+ int i, mode;
+
+ seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
+
+ pmc_for_each_mode(i, mode, pmcdev) {
+ seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
+ adjust_lpm_residency(pmcdev, offset + (4 * mode), lpm_adj_x2));
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_res);
+
+static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
+ u32 offset = pmcdev->map->lpm_status_offset;
+
+ pmc_core_lpm_display(pmcdev, NULL, s, offset, "STATUS", maps);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_sts_regs);
+
+static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
+ u32 offset = pmcdev->map->lpm_live_status_offset;
+
+ pmc_core_lpm_display(pmcdev, NULL, s, offset, "LIVE_STATUS", maps);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs);
+
+static void pmc_core_substate_req_header_show(struct seq_file *s)
+{
+ struct pmc_dev *pmcdev = s->private;
+ int i, mode;
+
+ seq_printf(s, "%30s |", "Element");
+ pmc_for_each_mode(i, mode, pmcdev)
+ seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
+
+ seq_printf(s, " %9s |\n", "Status");
+}
+
+static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
+ const struct pmc_bit_map *map;
+ const int num_maps = pmcdev->map->lpm_num_maps;
+ u32 sts_offset = pmcdev->map->lpm_status_offset;
+ u32 *lpm_req_regs = pmcdev->lpm_req_regs;
+ int mp;
+
+ /* Display the header */
+ pmc_core_substate_req_header_show(s);
+
+ /* Loop over maps */
+ for (mp = 0; mp < num_maps; mp++) {
+ u32 req_mask = 0;
+ u32 lpm_status;
+ int mode, idx, i, len = 32;
+
+ /*
+ * Capture the requirements and create a mask so that we only
+ * show an element if it's required for at least one of the
+ * enabled low power modes
+ */
+ pmc_for_each_mode(idx, mode, pmcdev)
+ req_mask |= lpm_req_regs[mp + (mode * num_maps)];
+
+ /* Get the last latched status for this map */
+ lpm_status = pmc_core_reg_read(pmcdev, sts_offset + (mp * 4));
+
+ /* Loop over elements in this map */
+ map = maps[mp];
+ for (i = 0; map[i].name && i < len; i++) {
+ u32 bit_mask = map[i].bit_mask;
+
+ if (!(bit_mask & req_mask))
+ /*
+ * Not required for any enabled states
+ * so don't display
+ */
+ continue;
+
+ /* Display the element name in the first column */
+ seq_printf(s, "%30s |", map[i].name);
+
+ /* Loop over the enabled states and display if required */
+ pmc_for_each_mode(idx, mode, pmcdev) {
+ if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask)
+ seq_printf(s, " %9s |",
+ "Required");
+ else
+ seq_printf(s, " %9s |", " ");
+ }
+
+ /* In Status column, show the last captured state of this agent */
+ if (lpm_status & bit_mask)
+ seq_printf(s, " %9s |", "Yes");
+ else
+ seq_printf(s, " %9s |", " ");
+
+ seq_puts(s, "\n");
+ }
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
+
+static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ bool c10;
+ u32 reg;
+ int idx, mode;
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
+ if (reg & LPM_STS_LATCH_MODE) {
+ seq_puts(s, "c10");
+ c10 = false;
+ } else {
+ seq_puts(s, "[c10]");
+ c10 = true;
+ }
+
+ pmc_for_each_mode(idx, mode, pmcdev) {
+ if ((BIT(mode) & reg) && !c10)
+ seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
+ else
+ seq_printf(s, " %s", pmc_lpm_modes[mode]);
+ }
+
+ seq_puts(s, " clear\n");
+
+ return 0;
+}
+
+static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct pmc_dev *pmcdev = s->private;
+ bool clear = false, c10 = false;
+ unsigned char buf[8];
+ int idx, m, mode;
+ u32 reg;
+
+ if (count > sizeof(buf) - 1)
+ return -EINVAL;
+ if (copy_from_user(buf, userbuf, count))
+ return -EFAULT;
+ buf[count] = '\0';
+
+ /*
+ * Allowed strings are:
+ * Any enabled substate, e.g. 'S0i2.0'
+ * 'c10'
+ * 'clear'
+ */
+ mode = sysfs_match_string(pmc_lpm_modes, buf);
+
+ /* Check string matches enabled mode */
+ pmc_for_each_mode(idx, m, pmcdev)
+ if (mode == m)
+ break;
+
+ if (mode != m || mode < 0) {
+ if (sysfs_streq(buf, "clear"))
+ clear = true;
+ else if (sysfs_streq(buf, "c10"))
+ c10 = true;
+ else
+ return -EINVAL;
+ }
+
+ if (clear) {
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset);
+ reg |= ETR3_CLEAR_LPM_EVENTS;
+ pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg);
+
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+ }
+
+ if (c10) {
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
+ reg &= ~LPM_STS_LATCH_MODE;
+ pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
+
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+ }
+
+ /*
+ * For LPM mode latching we set the latch enable bit and selected mode
+ * and clear everything else.
+ */
+ reg = LPM_STS_LATCH_MODE | BIT(mode);
+ mutex_lock(&pmcdev->lock);
+ pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+}
+DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode);
+
+static int pmc_core_pkgc_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ const struct pmc_bit_map *map = pmcdev->map->msr_sts;
+ u64 pcstate_count;
+ int index;
+
+ for (index = 0; map[index].name ; index++) {
+ if (rdmsrl_safe(map[index].bit_mask, &pcstate_count))
+ continue;
+
+ pcstate_count *= 1000;
+ do_div(pcstate_count, tsc_khz);
+ seq_printf(s, "%-8s : %llu\n", map[index].name,
+ pcstate_count);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc);
+
+static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
+{
+ int i, j;
+
+ if (!lpm_pri)
+ return false;
+ /*
+ * Each byte contains the priority level for 2 modes (7:4 and 3:0).
+ * In a 32 bit register this allows for describing 8 modes. Store the
+ * levels and look for values out of range.
+ */
+ for (i = 0; i < 8; i++) {
+ int level = lpm_pri & GENMASK(3, 0);
+
+ if (level >= LPM_MAX_NUM_MODES)
+ return false;
+
+ mode_order[i] = level;
+ lpm_pri >>= 4;
+ }
+
+ /* Check that we have unique values */
+ for (i = 0; i < LPM_MAX_NUM_MODES - 1; i++)
+ for (j = i + 1; j < LPM_MAX_NUM_MODES; j++)
+ if (mode_order[i] == mode_order[j])
+ return false;
+
+ return true;
+}
+
+static void pmc_core_get_low_power_modes(struct platform_device *pdev)
+{
+ struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
+ u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
+ u8 mode_order[LPM_MAX_NUM_MODES];
+ u32 lpm_pri;
+ u32 lpm_en;
+ int mode, i, p;
+
+ /* Use LPM Maps to indicate support for substates */
+ if (!pmcdev->map->lpm_num_maps)
+ return;
+
+ lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset);
+ pmcdev->num_lpm_modes = hweight32(lpm_en);
+
+ /* Read 32 bit LPM_PRI register */
+ lpm_pri = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_priority_offset);
+
+
+ /*
+ * If lpm_pri value passes verification, then override the default
+ * modes here. Otherwise stick with the default.
+ */
+ if (pmc_core_pri_verify(lpm_pri, mode_order))
+ /* Get list of modes in priority order */
+ for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++)
+ pri_order[mode_order[mode]] = mode;
+ else
+ dev_warn(&pdev->dev, "Assuming a default substate order for this platform\n");
+
+ /*
+ * Loop through all modes from lowest to highest priority,
+ * and capture all enabled modes in order
+ */
+ i = 0;
+ for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
+ int mode = pri_order[p];
+
+ if (!(BIT(mode) & lpm_en))
+ continue;
+
+ pmcdev->lpm_en_modes[i++] = mode;
+ }
+}
+
+static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
+{
+ debugfs_remove_recursive(pmcdev->dbgfs_dir);
+}
+
+static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
+{
+ struct dentry *dir;
+
+ dir = debugfs_create_dir("pmc_core", NULL);
+ pmcdev->dbgfs_dir = dir;
+
+ debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev,
+ &pmc_core_dev_state);
+
+ if (pmcdev->map->pfear_sts)
+ debugfs_create_file("pch_ip_power_gating_status", 0444, dir,
+ pmcdev, &pmc_core_ppfear_fops);
+
+ debugfs_create_file("ltr_ignore", 0644, dir, pmcdev,
+ &pmc_core_ltr_ignore_ops);
+
+ debugfs_create_file("ltr_show", 0444, dir, pmcdev, &pmc_core_ltr_fops);
+
+ debugfs_create_file("package_cstate_show", 0444, dir, pmcdev,
+ &pmc_core_pkgc_fops);
+
+ if (pmcdev->map->pll_sts)
+ debugfs_create_file("pll_status", 0444, dir, pmcdev,
+ &pmc_core_pll_fops);
+
+ if (pmcdev->map->mphy_sts)
+ debugfs_create_file("mphy_core_lanes_power_gating_status",
+ 0444, dir, pmcdev,
+ &pmc_core_mphy_pg_fops);
+
+ if (pmcdev->map->slps0_dbg_maps) {
+ debugfs_create_file("slp_s0_debug_status", 0444,
+ dir, pmcdev,
+ &pmc_core_slps0_dbg_fops);
+
+ debugfs_create_bool("slp_s0_dbg_latch", 0644,
+ dir, &slps0_dbg_latch);
+ }
+
+ if (pmcdev->map->lpm_en_offset) {
+ debugfs_create_file("substate_residencies", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_substate_res_fops);
+ }
+
+ if (pmcdev->map->lpm_status_offset) {
+ debugfs_create_file("substate_status_registers", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_substate_sts_regs_fops);
+ debugfs_create_file("substate_live_status_registers", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_substate_l_sts_regs_fops);
+ debugfs_create_file("lpm_latch_mode", 0644,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_lpm_latch_mode_fops);
+ }
+
+ if (pmcdev->lpm_req_regs) {
+ debugfs_create_file("substate_requirements", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_substate_req_regs_fops);
+ }
+}
+
+static const struct x86_cpu_id intel_pmc_core_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &spt_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &spt_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &spt_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &spt_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &cnp_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &icl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, &icl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &cnp_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &cnp_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, &icl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, &adl_reg_map),
+ {}
+};
+
+MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_ids);
+
+static const struct pci_device_id pmc_pci_ids[] = {
+ { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID) },
+ { }
+};
+
+/*
+ * This quirk can be used on those platforms where
+ * the platform BIOS enforces 24Mhz crystal to shutdown
+ * before PMC can assert SLP_S0#.
+ */
+static bool xtal_ignore;
+static int quirk_xtal_ignore(const struct dmi_system_id *id)
+{
+ xtal_ignore = true;
+ return 0;
+}
+
+static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev)
+{
+ u32 value;
+
+ value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset);
+ /* 24MHz Crystal Shutdown Qualification Disable */
+ value |= SPT_PMC_VRIC1_XTALSDQDIS;
+ /* Low Voltage Mode Enable */
+ value &= ~SPT_PMC_VRIC1_SLPS0LVEN;
+ pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value);
+}
+
+static const struct dmi_system_id pmc_core_dmi_table[] = {
+ {
+ .callback = quirk_xtal_ignore,
+ .ident = "HP Elite x2 1013 G3",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite x2 1013 G3"),
+ },
+ },
+ {}
+};
+
+static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev)
+{
+ dmi_check_system(pmc_core_dmi_table);
+
+ if (xtal_ignore)
+ pmc_core_xtal_ignore(pmcdev);
+}
+
+static int pmc_core_probe(struct platform_device *pdev)
+{
+ static bool device_initialized;
+ struct pmc_dev *pmcdev;
+ const struct x86_cpu_id *cpu_id;
+ u64 slp_s0_addr;
+
+ if (device_initialized)
+ return -ENODEV;
+
+ pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL);
+ if (!pmcdev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, pmcdev);
+
+ cpu_id = x86_match_cpu(intel_pmc_core_ids);
+ if (!cpu_id)
+ return -ENODEV;
+
+ pmcdev->map = (struct pmc_reg_map *)cpu_id->driver_data;
+
+ /*
+ * Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here
+ * Sunrisepoint PCH regmap can't be used. Use Cannon Lake PCH regmap
+ * in this case.
+ */
+ if (pmcdev->map == &spt_reg_map && !pci_dev_present(pmc_pci_ids))
+ pmcdev->map = &cnp_reg_map;
+
+ if (lpit_read_residency_count_address(&slp_s0_addr)) {
+ pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT;
+
+ if (page_is_ram(PHYS_PFN(pmcdev->base_addr)))
+ return -ENODEV;
+ } else {
+ pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset;
+ }
+
+ pmcdev->regbase = ioremap(pmcdev->base_addr,
+ pmcdev->map->regmap_length);
+ if (!pmcdev->regbase)
+ return -ENOMEM;
+
+ mutex_init(&pmcdev->lock);
+
+ pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev);
+ pmc_core_get_low_power_modes(pdev);
+ pmc_core_do_dmi_quirks(pmcdev);
+
+ if (pmcdev->map == &tgl_reg_map)
+ pmc_core_get_tgl_lpm_reqs(pdev);
+
+ /*
+ * On TGL and ADL, due to a hardware limitation, the GBE LTR blocks PC10
+ * when a cable is attached. Tell the PMC to ignore it.
+ */
+ if (pmcdev->map == &tgl_reg_map || pmcdev->map == &adl_reg_map) {
+ dev_dbg(&pdev->dev, "ignoring GBE LTR\n");
+ pmc_core_send_ltr_ignore(pmcdev, 3);
+ }
+
+ pmc_core_dbgfs_register(pmcdev);
+
+ device_initialized = true;
+ dev_info(&pdev->dev, " initialized\n");
+
+ return 0;
+}
+
+static int pmc_core_remove(struct platform_device *pdev)
+{
+ struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
+
+ pmc_core_dbgfs_unregister(pmcdev);
+ platform_set_drvdata(pdev, NULL);
+ mutex_destroy(&pmcdev->lock);
+ iounmap(pmcdev->regbase);
+ return 0;
+}
+
+static bool warn_on_s0ix_failures;
+module_param(warn_on_s0ix_failures, bool, 0644);
+MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures");
+
+static __maybe_unused int pmc_core_suspend(struct device *dev)
+{
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+
+ pmcdev->check_counters = false;
+
+ /* No warnings on S0ix failures */
+ if (!warn_on_s0ix_failures)
+ return 0;
+
+ /* Check if the syspend will actually use S0ix */
+ if (pm_suspend_via_firmware())
+ return 0;
+
+ /* Save PC10 residency for checking later */
+ if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pmcdev->pc10_counter))
+ return -EIO;
+
+ /* Save S0ix residency for checking later */
+ if (pmc_core_dev_state_get(pmcdev, &pmcdev->s0ix_counter))
+ return -EIO;
+
+ pmcdev->check_counters = true;
+ return 0;
+}
+
+static inline bool pmc_core_is_pc10_failed(struct pmc_dev *pmcdev)
+{
+ u64 pc10_counter;
+
+ if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pc10_counter))
+ return false;
+
+ if (pc10_counter == pmcdev->pc10_counter)
+ return true;
+
+ return false;
+}
+
+static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev)
+{
+ u64 s0ix_counter;
+
+ if (pmc_core_dev_state_get(pmcdev, &s0ix_counter))
+ return false;
+
+ if (s0ix_counter == pmcdev->s0ix_counter)
+ return true;
+
+ return false;
+}
+
+static __maybe_unused int pmc_core_resume(struct device *dev)
+{
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
+ int offset = pmcdev->map->lpm_status_offset;
+
+ if (!pmcdev->check_counters)
+ return 0;
+
+ if (!pmc_core_is_s0ix_failed(pmcdev))
+ return 0;
+
+ if (pmc_core_is_pc10_failed(pmcdev)) {
+ /* S0ix failed because of PC10 entry failure */
+ dev_info(dev, "CPU did not enter PC10!!! (PC10 cnt=0x%llx)\n",
+ pmcdev->pc10_counter);
+ return 0;
+ }
+
+ /* The real interesting case - S0ix failed - lets ask PMC why. */
+ dev_warn(dev, "CPU did not enter SLP_S0!!! (S0ix cnt=%llu)\n",
+ pmcdev->s0ix_counter);
+ if (pmcdev->map->slps0_dbg_maps)
+ pmc_core_slps0_display(pmcdev, dev, NULL);
+ if (pmcdev->map->lpm_sts)
+ pmc_core_lpm_display(pmcdev, dev, NULL, offset, "STATUS", maps);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pmc_core_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume)
+};
+
+static const struct acpi_device_id pmc_core_acpi_ids[] = {
+ {"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids);
+
+static struct platform_driver pmc_core_driver = {
+ .driver = {
+ .name = "intel_pmc_core",
+ .acpi_match_table = ACPI_PTR(pmc_core_acpi_ids),
+ .pm = &pmc_core_pm_ops,
+ .dev_groups = pmc_dev_groups,
+ },
+ .probe = pmc_core_probe,
+ .remove = pmc_core_remove,
+};
+
+module_platform_driver(pmc_core_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel PMC Core Driver");
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
new file mode 100644
index 000000000000..a46d3b53bf61
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -0,0 +1,363 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel Core SoC Power Management Controller Header File
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
+ * Vishwanath Somayaji <vishwanath.somayaji@intel.com>
+ */
+
+#ifndef PMC_CORE_H
+#define PMC_CORE_H
+
+#include <linux/bits.h>
+
+#define PMC_BASE_ADDR_DEFAULT 0xFE000000
+
+/* Sunrise Point Power Management Controller PCI Device ID */
+#define SPT_PMC_PCI_DEVICE_ID 0x9d21
+#define SPT_PMC_BASE_ADDR_OFFSET 0x48
+#define SPT_PMC_SLP_S0_RES_COUNTER_OFFSET 0x13c
+#define SPT_PMC_PM_CFG_OFFSET 0x18
+#define SPT_PMC_PM_STS_OFFSET 0x1c
+#define SPT_PMC_MTPMC_OFFSET 0x20
+#define SPT_PMC_MFPMC_OFFSET 0x38
+#define SPT_PMC_LTR_IGNORE_OFFSET 0x30C
+#define SPT_PMC_VRIC1_OFFSET 0x31c
+#define SPT_PMC_MPHY_CORE_STS_0 0x1143
+#define SPT_PMC_MPHY_CORE_STS_1 0x1142
+#define SPT_PMC_MPHY_COM_STS_0 0x1155
+#define SPT_PMC_MMIO_REG_LEN 0x1000
+#define SPT_PMC_SLP_S0_RES_COUNTER_STEP 0x68
+#define PMC_BASE_ADDR_MASK ~(SPT_PMC_MMIO_REG_LEN - 1)
+#define MTPMC_MASK 0xffff0000
+#define PPFEAR_MAX_NUM_ENTRIES 12
+#define SPT_PPFEAR_NUM_ENTRIES 5
+#define SPT_PMC_READ_DISABLE_BIT 0x16
+#define SPT_PMC_MSG_FULL_STS_BIT 0x18
+#define NUM_RETRIES 100
+#define SPT_NUM_IP_IGN_ALLOWED 17
+
+#define SPT_PMC_LTR_CUR_PLT 0x350
+#define SPT_PMC_LTR_CUR_ASLT 0x354
+#define SPT_PMC_LTR_SPA 0x360
+#define SPT_PMC_LTR_SPB 0x364
+#define SPT_PMC_LTR_SATA 0x368
+#define SPT_PMC_LTR_GBE 0x36C
+#define SPT_PMC_LTR_XHCI 0x370
+#define SPT_PMC_LTR_RESERVED 0x374
+#define SPT_PMC_LTR_ME 0x378
+#define SPT_PMC_LTR_EVA 0x37C
+#define SPT_PMC_LTR_SPC 0x380
+#define SPT_PMC_LTR_AZ 0x384
+#define SPT_PMC_LTR_LPSS 0x38C
+#define SPT_PMC_LTR_CAM 0x390
+#define SPT_PMC_LTR_SPD 0x394
+#define SPT_PMC_LTR_SPE 0x398
+#define SPT_PMC_LTR_ESPI 0x39C
+#define SPT_PMC_LTR_SCC 0x3A0
+#define SPT_PMC_LTR_ISH 0x3A4
+
+/* Sunrise Point: PGD PFET Enable Ack Status Registers */
+enum ppfear_regs {
+ SPT_PMC_XRAM_PPFEAR0A = 0x590,
+ SPT_PMC_XRAM_PPFEAR0B,
+ SPT_PMC_XRAM_PPFEAR0C,
+ SPT_PMC_XRAM_PPFEAR0D,
+ SPT_PMC_XRAM_PPFEAR1A,
+};
+
+#define SPT_PMC_BIT_PMC BIT(0)
+#define SPT_PMC_BIT_OPI BIT(1)
+#define SPT_PMC_BIT_SPI BIT(2)
+#define SPT_PMC_BIT_XHCI BIT(3)
+#define SPT_PMC_BIT_SPA BIT(4)
+#define SPT_PMC_BIT_SPB BIT(5)
+#define SPT_PMC_BIT_SPC BIT(6)
+#define SPT_PMC_BIT_GBE BIT(7)
+
+#define SPT_PMC_BIT_SATA BIT(0)
+#define SPT_PMC_BIT_HDA_PGD0 BIT(1)
+#define SPT_PMC_BIT_HDA_PGD1 BIT(2)
+#define SPT_PMC_BIT_HDA_PGD2 BIT(3)
+#define SPT_PMC_BIT_HDA_PGD3 BIT(4)
+#define SPT_PMC_BIT_RSVD_0B BIT(5)
+#define SPT_PMC_BIT_LPSS BIT(6)
+#define SPT_PMC_BIT_LPC BIT(7)
+
+#define SPT_PMC_BIT_SMB BIT(0)
+#define SPT_PMC_BIT_ISH BIT(1)
+#define SPT_PMC_BIT_P2SB BIT(2)
+#define SPT_PMC_BIT_DFX BIT(3)
+#define SPT_PMC_BIT_SCC BIT(4)
+#define SPT_PMC_BIT_RSVD_0C BIT(5)
+#define SPT_PMC_BIT_FUSE BIT(6)
+#define SPT_PMC_BIT_CAMREA BIT(7)
+
+#define SPT_PMC_BIT_RSVD_0D BIT(0)
+#define SPT_PMC_BIT_USB3_OTG BIT(1)
+#define SPT_PMC_BIT_EXI BIT(2)
+#define SPT_PMC_BIT_CSE BIT(3)
+#define SPT_PMC_BIT_CSME_KVM BIT(4)
+#define SPT_PMC_BIT_CSME_PMT BIT(5)
+#define SPT_PMC_BIT_CSME_CLINK BIT(6)
+#define SPT_PMC_BIT_CSME_PTIO BIT(7)
+
+#define SPT_PMC_BIT_CSME_USBR BIT(0)
+#define SPT_PMC_BIT_CSME_SUSRAM BIT(1)
+#define SPT_PMC_BIT_CSME_SMT BIT(2)
+#define SPT_PMC_BIT_RSVD_1A BIT(3)
+#define SPT_PMC_BIT_CSME_SMS2 BIT(4)
+#define SPT_PMC_BIT_CSME_SMS1 BIT(5)
+#define SPT_PMC_BIT_CSME_RTC BIT(6)
+#define SPT_PMC_BIT_CSME_PSF BIT(7)
+
+#define SPT_PMC_BIT_MPHY_LANE0 BIT(0)
+#define SPT_PMC_BIT_MPHY_LANE1 BIT(1)
+#define SPT_PMC_BIT_MPHY_LANE2 BIT(2)
+#define SPT_PMC_BIT_MPHY_LANE3 BIT(3)
+#define SPT_PMC_BIT_MPHY_LANE4 BIT(4)
+#define SPT_PMC_BIT_MPHY_LANE5 BIT(5)
+#define SPT_PMC_BIT_MPHY_LANE6 BIT(6)
+#define SPT_PMC_BIT_MPHY_LANE7 BIT(7)
+
+#define SPT_PMC_BIT_MPHY_LANE8 BIT(0)
+#define SPT_PMC_BIT_MPHY_LANE9 BIT(1)
+#define SPT_PMC_BIT_MPHY_LANE10 BIT(2)
+#define SPT_PMC_BIT_MPHY_LANE11 BIT(3)
+#define SPT_PMC_BIT_MPHY_LANE12 BIT(4)
+#define SPT_PMC_BIT_MPHY_LANE13 BIT(5)
+#define SPT_PMC_BIT_MPHY_LANE14 BIT(6)
+#define SPT_PMC_BIT_MPHY_LANE15 BIT(7)
+
+#define SPT_PMC_BIT_MPHY_CMN_LANE0 BIT(0)
+#define SPT_PMC_BIT_MPHY_CMN_LANE1 BIT(1)
+#define SPT_PMC_BIT_MPHY_CMN_LANE2 BIT(2)
+#define SPT_PMC_BIT_MPHY_CMN_LANE3 BIT(3)
+
+#define SPT_PMC_VRIC1_SLPS0LVEN BIT(13)
+#define SPT_PMC_VRIC1_XTALSDQDIS BIT(22)
+
+/* Cannonlake Power Management Controller register offsets */
+#define CNP_PMC_SLPS0_DBG_OFFSET 0x10B4
+#define CNP_PMC_PM_CFG_OFFSET 0x1818
+#define CNP_PMC_SLP_S0_RES_COUNTER_OFFSET 0x193C
+#define CNP_PMC_LTR_IGNORE_OFFSET 0x1B0C
+/* Cannonlake: PGD PFET Enable Ack Status Register(s) start */
+#define CNP_PMC_HOST_PPFEAR0A 0x1D90
+
+#define CNP_PMC_LATCH_SLPS0_EVENTS BIT(31)
+
+#define CNP_PMC_MMIO_REG_LEN 0x2000
+#define CNP_PPFEAR_NUM_ENTRIES 8
+#define CNP_PMC_READ_DISABLE_BIT 22
+#define CNP_NUM_IP_IGN_ALLOWED 19
+#define CNP_PMC_LTR_CUR_PLT 0x1B50
+#define CNP_PMC_LTR_CUR_ASLT 0x1B54
+#define CNP_PMC_LTR_SPA 0x1B60
+#define CNP_PMC_LTR_SPB 0x1B64
+#define CNP_PMC_LTR_SATA 0x1B68
+#define CNP_PMC_LTR_GBE 0x1B6C
+#define CNP_PMC_LTR_XHCI 0x1B70
+#define CNP_PMC_LTR_RESERVED 0x1B74
+#define CNP_PMC_LTR_ME 0x1B78
+#define CNP_PMC_LTR_EVA 0x1B7C
+#define CNP_PMC_LTR_SPC 0x1B80
+#define CNP_PMC_LTR_AZ 0x1B84
+#define CNP_PMC_LTR_LPSS 0x1B8C
+#define CNP_PMC_LTR_CAM 0x1B90
+#define CNP_PMC_LTR_SPD 0x1B94
+#define CNP_PMC_LTR_SPE 0x1B98
+#define CNP_PMC_LTR_ESPI 0x1B9C
+#define CNP_PMC_LTR_SCC 0x1BA0
+#define CNP_PMC_LTR_ISH 0x1BA4
+#define CNP_PMC_LTR_CNV 0x1BF0
+#define CNP_PMC_LTR_EMMC 0x1BF4
+#define CNP_PMC_LTR_UFSX2 0x1BF8
+
+#define LTR_DECODED_VAL GENMASK(9, 0)
+#define LTR_DECODED_SCALE GENMASK(12, 10)
+#define LTR_REQ_SNOOP BIT(15)
+#define LTR_REQ_NONSNOOP BIT(31)
+
+#define ICL_PPFEAR_NUM_ENTRIES 9
+#define ICL_NUM_IP_IGN_ALLOWED 20
+#define ICL_PMC_LTR_WIGIG 0x1BFC
+#define ICL_PMC_SLP_S0_RES_COUNTER_STEP 0x64
+
+#define LPM_MAX_NUM_MODES 8
+#define LPM_DEFAULT_PRI { 7, 6, 2, 5, 4, 1, 3, 0 }
+
+#define GET_X2_COUNTER(v) ((v) >> 1)
+#define LPM_STS_LATCH_MODE BIT(31)
+
+#define TGL_PMC_SLP_S0_RES_COUNTER_STEP 0x7A
+#define TGL_PMC_LTR_THC0 0x1C04
+#define TGL_PMC_LTR_THC1 0x1C08
+#define TGL_NUM_IP_IGN_ALLOWED 23
+#define TGL_PMC_LPM_RES_COUNTER_STEP_X2 61 /* 30.5us * 2 */
+
+#define ADL_PMC_LTR_SPF 0x1C00
+#define ADL_NUM_IP_IGN_ALLOWED 23
+#define ADL_PMC_SLP_S0_RES_COUNTER_OFFSET 0x1098
+
+/*
+ * Tigerlake Power Management Controller register offsets
+ */
+#define TGL_LPM_STS_LATCH_EN_OFFSET 0x1C34
+#define TGL_LPM_EN_OFFSET 0x1C78
+#define TGL_LPM_RESIDENCY_OFFSET 0x1C80
+
+/* Tigerlake Low Power Mode debug registers */
+#define TGL_LPM_STATUS_OFFSET 0x1C3C
+#define TGL_LPM_LIVE_STATUS_OFFSET 0x1C5C
+#define TGL_LPM_PRI_OFFSET 0x1C7C
+#define TGL_LPM_NUM_MAPS 6
+
+/* Extended Test Mode Register 3 (CNL and later) */
+#define ETR3_OFFSET 0x1048
+#define ETR3_CF9GR BIT(20)
+#define ETR3_CF9LOCK BIT(31)
+
+/* Extended Test Mode Register LPM bits (TGL and later */
+#define ETR3_CLEAR_LPM_EVENTS BIT(28)
+
+/* Alder Lake Power Management Controller register offsets */
+#define ADL_LPM_EN_OFFSET 0x179C
+#define ADL_LPM_RESIDENCY_OFFSET 0x17A4
+#define ADL_LPM_NUM_MODES 2
+#define ADL_LPM_NUM_MAPS 14
+
+/* Alder Lake Low Power Mode debug registers */
+#define ADL_LPM_STATUS_OFFSET 0x170C
+#define ADL_LPM_PRI_OFFSET 0x17A0
+#define ADL_LPM_STATUS_LATCH_EN_OFFSET 0x1704
+#define ADL_LPM_LIVE_STATUS_OFFSET 0x1764
+
+const char *pmc_lpm_modes[] = {
+ "S0i2.0",
+ "S0i2.1",
+ "S0i2.2",
+ "S0i3.0",
+ "S0i3.1",
+ "S0i3.2",
+ "S0i3.3",
+ "S0i3.4",
+ NULL
+};
+
+struct pmc_bit_map {
+ const char *name;
+ u32 bit_mask;
+};
+
+/**
+ * struct pmc_reg_map - Structure used to define parameter unique to a
+ PCH family
+ * @pfear_sts: Maps name of IP block to PPFEAR* bit
+ * @mphy_sts: Maps name of MPHY lane to MPHY status lane status bit
+ * @pll_sts: Maps name of PLL to corresponding bit status
+ * @slps0_dbg_maps: Array of SLP_S0_DBG* registers containing debug info
+ * @ltr_show_sts: Maps PCH IP Names to their MMIO register offsets
+ * @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency
+ * @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit
+ * @regmap_length: Length of memory to map from PWRMBASE address to access
+ * @ppfear0_offset: PWRMBASE offset to to read PPFEAR*
+ * @ppfear_buckets: Number of 8 bits blocks to read all IP blocks from
+ * PPFEAR
+ * @pm_cfg_offset: PWRMBASE offset to PM_CFG register
+ * @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE
+ * @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG*
+ *
+ * Each PCH has unique set of register offsets and bit indexes. This structure
+ * captures them to have a common implementation.
+ */
+struct pmc_reg_map {
+ const struct pmc_bit_map **pfear_sts;
+ const struct pmc_bit_map *mphy_sts;
+ const struct pmc_bit_map *pll_sts;
+ const struct pmc_bit_map **slps0_dbg_maps;
+ const struct pmc_bit_map *ltr_show_sts;
+ const struct pmc_bit_map *msr_sts;
+ const struct pmc_bit_map **lpm_sts;
+ const u32 slp_s0_offset;
+ const int slp_s0_res_counter_step;
+ const u32 ltr_ignore_offset;
+ const int regmap_length;
+ const u32 ppfear0_offset;
+ const int ppfear_buckets;
+ const u32 pm_cfg_offset;
+ const int pm_read_disable_bit;
+ const u32 slps0_dbg_offset;
+ const u32 ltr_ignore_max;
+ const u32 pm_vric1_offset;
+ /* Low Power Mode registers */
+ const int lpm_num_maps;
+ const int lpm_num_modes;
+ const int lpm_res_counter_step_x2;
+ const u32 lpm_sts_latch_en_offset;
+ const u32 lpm_en_offset;
+ const u32 lpm_priority_offset;
+ const u32 lpm_residency_offset;
+ const u32 lpm_status_offset;
+ const u32 lpm_live_status_offset;
+ const u32 etr3_offset;
+};
+
+/**
+ * struct pmc_dev - pmc device structure
+ * @base_addr: contains pmc base address
+ * @regbase: pointer to io-remapped memory location
+ * @map: pointer to pmc_reg_map struct that contains platform
+ * specific attributes
+ * @dbgfs_dir: path to debugfs interface
+ * @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers
+ * used to read MPHY PG and PLL status are available
+ * @mutex_lock: mutex to complete one transcation
+ * @check_counters: On resume, check if counters are getting incremented
+ * @pc10_counter: PC10 residency counter
+ * @s0ix_counter: S0ix residency (step adjusted)
+ * @num_lpm_modes: Count of enabled modes
+ * @lpm_en_modes: Array of enabled modes from lowest to highest priority
+ * @lpm_req_regs: List of substate requirements
+ *
+ * pmc_dev contains info about power management controller device.
+ */
+struct pmc_dev {
+ u32 base_addr;
+ void __iomem *regbase;
+ const struct pmc_reg_map *map;
+ struct dentry *dbgfs_dir;
+ int pmc_xram_read_bit;
+ struct mutex lock; /* generic mutex lock for PMC Core */
+
+ bool check_counters; /* Check for counter increments on resume */
+ u64 pc10_counter;
+ u64 s0ix_counter;
+ int num_lpm_modes;
+ int lpm_en_modes[LPM_MAX_NUM_MODES];
+ u32 *lpm_req_regs;
+};
+
+#define pmc_for_each_mode(i, mode, pmcdev) \
+ for (i = 0, mode = pmcdev->lpm_en_modes[i]; \
+ i < pmcdev->num_lpm_modes; \
+ i++, mode = pmcdev->lpm_en_modes[i])
+
+#define DEFINE_PMC_CORE_ATTR_WRITE(__name) \
+static int __name ## _open(struct inode *inode, struct file *file) \
+{ \
+ return single_open(file, __name ## _show, inode->i_private); \
+} \
+ \
+static const struct file_operations __name ## _fops = { \
+ .owner = THIS_MODULE, \
+ .open = __name ## _open, \
+ .read = seq_read, \
+ .write = __name ## _write, \
+ .release = single_release, \
+}
+
+#endif /* PMC_CORE_H */
diff --git a/drivers/platform/x86/intel/pmc/pltdrv.c b/drivers/platform/x86/intel/pmc/pltdrv.c
new file mode 100644
index 000000000000..73797680b895
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/pltdrv.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Intel PMC Core platform init
+ * Copyright (c) 2019, Google Inc.
+ * Author - Rajat Jain
+ *
+ * This code instantiates platform devices for intel_pmc_core driver, only
+ * on supported platforms that may not have the ACPI devices in the ACPI tables.
+ * No new platforms should be added here, because we expect that new platforms
+ * should all have the ACPI device, which is the preferred way of enumeration.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+static void intel_pmc_core_release(struct device *dev)
+{
+ kfree(dev);
+}
+
+static struct platform_device *pmc_core_device;
+
+/*
+ * intel_pmc_core_platform_ids is the list of platforms where we want to
+ * instantiate the platform_device if not already instantiated. This is
+ * different than intel_pmc_core_ids in intel_pmc_core.c which is the
+ * list of platforms that the driver supports for pmc_core device. The
+ * other list may grow, but this list should not.
+ */
+static const struct x86_cpu_id intel_pmc_core_platform_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, &pmc_core_device),
+ X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, &pmc_core_device),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids);
+
+static int __init pmc_core_platform_init(void)
+{
+ int retval;
+
+ /* Skip creating the platform device if ACPI already has a device */
+ if (acpi_dev_present("INT33A1", NULL, -1))
+ return -ENODEV;
+
+ if (!x86_match_cpu(intel_pmc_core_platform_ids))
+ return -ENODEV;
+
+ pmc_core_device = kzalloc(sizeof(*pmc_core_device), GFP_KERNEL);
+ if (!pmc_core_device)
+ return -ENOMEM;
+
+ pmc_core_device->name = "intel_pmc_core";
+ pmc_core_device->dev.release = intel_pmc_core_release;
+
+ retval = platform_device_register(pmc_core_device);
+ if (retval)
+ kfree(pmc_core_device);
+
+ return retval;
+}
+
+static void __exit pmc_core_platform_exit(void)
+{
+ platform_device_unregister(pmc_core_device);
+}
+
+module_init(pmc_core_platform_init);
+module_exit(pmc_core_platform_exit);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig
new file mode 100644
index 000000000000..d630f883a717
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/Kconfig
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel Platform Monitoring Technology drivers
+#
+
+config INTEL_PMT_CLASS
+ tristate
+ help
+ The Intel Platform Monitoring Technology (PMT) class driver provides
+ the basic sysfs interface and file hierarchy used by PMT devices.
+
+ For more information, see:
+ <file:Documentation/ABI/testing/sysfs-class-intel_pmt>
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_class.
+
+config INTEL_PMT_TELEMETRY
+ tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
+ depends on MFD_INTEL_PMT
+ select INTEL_PMT_CLASS
+ help
+ The Intel Platform Monitory Technology (PMT) Telemetry driver provides
+ access to hardware telemetry metrics on devices that support the
+ feature.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_telemetry.
+
+config INTEL_PMT_CRASHLOG
+ tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver"
+ depends on MFD_INTEL_PMT
+ select INTEL_PMT_CLASS
+ help
+ The Intel Platform Monitoring Technology (PMT) crashlog driver provides
+ access to hardware crashlog capabilities on devices that support the
+ feature.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel_pmt_crashlog.
diff --git a/drivers/platform/x86/intel/pmt/Makefile b/drivers/platform/x86/intel/pmt/Makefile
new file mode 100644
index 000000000000..279e158c7c23
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/intel/pmt
+# Intel Platform Monitoring Technology Drivers
+#
+
+obj-$(CONFIG_INTEL_PMT_CLASS) += pmt_class.o
+pmt_class-y := class.o
+obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o
+pmt_telemetry-y := telemetry.o
+obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o
+pmt_crashlog-y := crashlog.o
diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
new file mode 100644
index 000000000000..659b1073033c
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Telemetry driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+#include "class.h"
+
+#define PMT_XA_START 0
+#define PMT_XA_MAX INT_MAX
+#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
+
+/*
+ * Early implementations of PMT on client platforms have some
+ * differences from the server platforms (which use the Out Of Band
+ * Management Services Module OOBMSM). This list tracks those
+ * platforms as needed to handle those differences. Newer client
+ * platforms are expected to be fully compatible with server.
+ */
+static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
+ { PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
+ { PCI_VDEVICE(INTEL, 0x490e) }, /* DG1 */
+ { PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
+ { }
+};
+
+bool intel_pmt_is_early_client_hw(struct device *dev)
+{
+ struct pci_dev *parent = to_pci_dev(dev->parent);
+
+ return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
+}
+EXPORT_SYMBOL_GPL(intel_pmt_is_early_client_hw);
+
+/*
+ * sysfs
+ */
+static ssize_t
+intel_pmt_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t off,
+ size_t count)
+{
+ struct intel_pmt_entry *entry = container_of(attr,
+ struct intel_pmt_entry,
+ pmt_bin_attr);
+
+ if (off < 0)
+ return -EINVAL;
+
+ if (off >= entry->size)
+ return 0;
+
+ if (count > entry->size - off)
+ count = entry->size - off;
+
+ memcpy_fromio(buf, entry->base + off, count);
+
+ return count;
+}
+
+static int
+intel_pmt_mmap(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, struct vm_area_struct *vma)
+{
+ struct intel_pmt_entry *entry = container_of(attr,
+ struct intel_pmt_entry,
+ pmt_bin_attr);
+ unsigned long vsize = vma->vm_end - vma->vm_start;
+ struct device *dev = kobj_to_dev(kobj);
+ unsigned long phys = entry->base_addr;
+ unsigned long pfn = PFN_DOWN(phys);
+ unsigned long psize;
+
+ if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
+ return -EROFS;
+
+ psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE;
+ if (vsize > psize) {
+ dev_err(dev, "Requested mmap size is too large\n");
+ return -EINVAL;
+ }
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ if (io_remap_pfn_range(vma, vma->vm_start, pfn,
+ vsize, vma->vm_page_prot))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static ssize_t
+guid_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "0x%x\n", entry->guid);
+}
+static DEVICE_ATTR_RO(guid);
+
+static ssize_t size_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%zu\n", entry->size);
+}
+static DEVICE_ATTR_RO(size);
+
+static ssize_t
+offset_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
+}
+static DEVICE_ATTR_RO(offset);
+
+static struct attribute *intel_pmt_attrs[] = {
+ &dev_attr_guid.attr,
+ &dev_attr_size.attr,
+ &dev_attr_offset.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(intel_pmt);
+
+static struct class intel_pmt_class = {
+ .name = "intel_pmt",
+ .owner = THIS_MODULE,
+ .dev_groups = intel_pmt_groups,
+};
+
+static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev,
+ struct resource *disc_res)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev->parent);
+ u8 bir;
+
+ /*
+ * The base offset should always be 8 byte aligned.
+ *
+ * For non-local access types the lower 3 bits of base offset
+ * contains the index of the base address register where the
+ * telemetry can be found.
+ */
+ bir = GET_BIR(header->base_offset);
+
+ /* Local access and BARID only for now */
+ switch (header->access_type) {
+ case ACCESS_LOCAL:
+ if (bir) {
+ dev_err(dev,
+ "Unsupported BAR index %d for access type %d\n",
+ bir, header->access_type);
+ return -EINVAL;
+ }
+ /*
+ * For access_type LOCAL, the base address is as follows:
+ * base address = end of discovery region + base offset
+ */
+ entry->base_addr = disc_res->end + 1 + header->base_offset;
+
+ /*
+ * Some hardware use a different calculation for the base address
+ * when access_type == ACCESS_LOCAL. On the these systems
+ * ACCCESS_LOCAL refers to an address in the same BAR as the
+ * header but at a fixed offset. But as the header address was
+ * supplied to the driver, we don't know which BAR it was in.
+ * So search for the bar whose range includes the header address.
+ */
+ if (intel_pmt_is_early_client_hw(dev)) {
+ int i;
+
+ entry->base_addr = 0;
+ for (i = 0; i < 6; i++)
+ if (disc_res->start >= pci_resource_start(pci_dev, i) &&
+ (disc_res->start <= pci_resource_end(pci_dev, i))) {
+ entry->base_addr = pci_resource_start(pci_dev, i) +
+ header->base_offset;
+ break;
+ }
+ if (!entry->base_addr)
+ return -EINVAL;
+ }
+
+ break;
+ case ACCESS_BARID:
+ /*
+ * If another BAR was specified then the base offset
+ * represents the offset within that BAR. SO retrieve the
+ * address from the parent PCI device and add offset.
+ */
+ entry->base_addr = pci_resource_start(pci_dev, bir) +
+ GET_ADDRESS(header->base_offset);
+ break;
+ default:
+ dev_err(dev, "Unsupported access type %d\n",
+ header->access_type);
+ return -EINVAL;
+ }
+
+ entry->guid = header->guid;
+ entry->size = header->size;
+
+ return 0;
+}
+
+static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct device *parent)
+{
+ struct resource res = {0};
+ struct device *dev;
+ int ret;
+
+ ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry,
+ "%s%d", ns->name, entry->devid);
+
+ if (IS_ERR(dev)) {
+ dev_err(parent, "Could not create %s%d device node\n",
+ ns->name, entry->devid);
+ ret = PTR_ERR(dev);
+ goto fail_dev_create;
+ }
+
+ entry->kobj = &dev->kobj;
+
+ if (ns->attr_grp) {
+ ret = sysfs_create_group(entry->kobj, ns->attr_grp);
+ if (ret)
+ goto fail_sysfs;
+ }
+
+ /* if size is 0 assume no data buffer, so no file needed */
+ if (!entry->size)
+ return 0;
+
+ res.start = entry->base_addr;
+ res.end = res.start + entry->size - 1;
+ res.flags = IORESOURCE_MEM;
+
+ entry->base = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(entry->base)) {
+ ret = PTR_ERR(entry->base);
+ goto fail_ioremap;
+ }
+
+ sysfs_bin_attr_init(&entry->pmt_bin_attr);
+ entry->pmt_bin_attr.attr.name = ns->name;
+ entry->pmt_bin_attr.attr.mode = 0440;
+ entry->pmt_bin_attr.mmap = intel_pmt_mmap;
+ entry->pmt_bin_attr.read = intel_pmt_read;
+ entry->pmt_bin_attr.size = entry->size;
+
+ ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
+ if (!ret)
+ return 0;
+
+fail_ioremap:
+ if (ns->attr_grp)
+ sysfs_remove_group(entry->kobj, ns->attr_grp);
+fail_sysfs:
+ device_unregister(dev);
+fail_dev_create:
+ xa_erase(ns->xa, entry->devid);
+
+ return ret;
+}
+
+int intel_pmt_dev_create(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct platform_device *pdev, int idx)
+{
+ struct intel_pmt_header header;
+ struct resource *disc_res;
+ int ret = -ENODEV;
+
+ disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx);
+ if (!disc_res)
+ return ret;
+
+ entry->disc_table = devm_platform_ioremap_resource(pdev, idx);
+ if (IS_ERR(entry->disc_table))
+ return PTR_ERR(entry->disc_table);
+
+ ret = ns->pmt_header_decode(entry, &header, &pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res);
+ if (ret)
+ return ret;
+
+ return intel_pmt_dev_register(entry, ns, &pdev->dev);
+
+}
+EXPORT_SYMBOL_GPL(intel_pmt_dev_create);
+
+void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns)
+{
+ struct device *dev = kobj_to_dev(entry->kobj);
+
+ if (entry->size)
+ sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
+
+ if (ns->attr_grp)
+ sysfs_remove_group(entry->kobj, ns->attr_grp);
+
+ device_unregister(dev);
+ xa_erase(ns->xa, entry->devid);
+}
+EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy);
+
+static int __init pmt_class_init(void)
+{
+ return class_register(&intel_pmt_class);
+}
+
+static void __exit pmt_class_exit(void)
+{
+ class_unregister(&intel_pmt_class);
+}
+
+module_init(pmt_class_init);
+module_exit(pmt_class_exit);
+
+MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Class driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h
new file mode 100644
index 000000000000..1337019c2873
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/class.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_PMT_CLASS_H
+#define _INTEL_PMT_CLASS_H
+
+#include <linux/platform_device.h>
+#include <linux/xarray.h>
+#include <linux/types.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+/* PMT access types */
+#define ACCESS_BARID 2
+#define ACCESS_LOCAL 3
+
+/* PMT discovery base address/offset register layout */
+#define GET_BIR(v) ((v) & GENMASK(2, 0))
+#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
+
+struct intel_pmt_entry {
+ struct bin_attribute pmt_bin_attr;
+ struct kobject *kobj;
+ void __iomem *disc_table;
+ void __iomem *base;
+ unsigned long base_addr;
+ size_t size;
+ u32 guid;
+ int devid;
+};
+
+struct intel_pmt_header {
+ u32 base_offset;
+ u32 size;
+ u32 guid;
+ u8 access_type;
+};
+
+struct intel_pmt_namespace {
+ const char *name;
+ struct xarray *xa;
+ const struct attribute_group *attr_grp;
+ int (*pmt_header_decode)(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev);
+};
+
+bool intel_pmt_is_early_client_hw(struct device *dev);
+int intel_pmt_dev_create(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns,
+ struct platform_device *pdev, int idx);
+void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
+ struct intel_pmt_namespace *ns);
+#endif
diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c
new file mode 100644
index 000000000000..1c1021f04d3c
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/crashlog.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitoring Technology Crashlog driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/overflow.h>
+
+#include "class.h"
+
+#define DRV_NAME "pmt_crashlog"
+
+/* Crashlog discovery header types */
+#define CRASH_TYPE_OOBMSM 1
+
+/* Control Flags */
+#define CRASHLOG_FLAG_DISABLE BIT(28)
+
+/*
+ * Bits 29 and 30 control the state of bit 31.
+ *
+ * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
+ * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
+ * Bit 31 is the read-only status with a 1 indicating log is complete.
+ */
+#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29)
+#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30)
+#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
+#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
+
+/* Crashlog Discovery Header */
+#define CONTROL_OFFSET 0x0
+#define GUID_OFFSET 0x4
+#define BASE_OFFSET 0x8
+#define SIZE_OFFSET 0xC
+#define GET_ACCESS(v) ((v) & GENMASK(3, 0))
+#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4)
+#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16)
+/* size is in bytes */
+#define GET_SIZE(v) ((v) * sizeof(u32))
+
+struct crashlog_entry {
+ /* entry must be first member of struct */
+ struct intel_pmt_entry entry;
+ struct mutex control_mutex;
+};
+
+struct pmt_crashlog_priv {
+ int num_entries;
+ struct crashlog_entry entry[];
+};
+
+/*
+ * I/O
+ */
+static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* return current value of the crashlog complete flag */
+ return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
+}
+
+static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* return current value of the crashlog disabled flag */
+ return !!(control & CRASHLOG_FLAG_DISABLE);
+}
+
+static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
+{
+ u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
+ u32 crash_type, version;
+
+ crash_type = GET_TYPE(discovery_header);
+ version = GET_VERSION(discovery_header);
+
+ /*
+ * Currently we only recognize OOBMSM version 0 devices.
+ * We can ignore all other crashlog devices in the system.
+ */
+ return crash_type == CRASH_TYPE_OOBMSM && version == 0;
+}
+
+static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
+ bool disable)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ /* clear trigger bits so we are only modifying disable flag */
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+
+ if (disable)
+ control |= CRASHLOG_FLAG_DISABLE;
+ else
+ control &= ~CRASHLOG_FLAG_DISABLE;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+ control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
+{
+ u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+
+ control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+ control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
+
+ writel(control, entry->disc_table + CONTROL_OFFSET);
+}
+
+/*
+ * sysfs
+ */
+static ssize_t
+enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry = dev_get_drvdata(dev);
+ int enabled = !pmt_crashlog_disabled(entry);
+
+ return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t
+enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct crashlog_entry *entry;
+ bool enabled;
+ int result;
+
+ entry = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &enabled);
+ if (result)
+ return result;
+
+ mutex_lock(&entry->control_mutex);
+ pmt_crashlog_set_disable(&entry->entry, !enabled);
+ mutex_unlock(&entry->control_mutex);
+
+ return count;
+}
+static DEVICE_ATTR_RW(enable);
+
+static ssize_t
+trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct intel_pmt_entry *entry;
+ int trigger;
+
+ entry = dev_get_drvdata(dev);
+ trigger = pmt_crashlog_complete(entry);
+
+ return sprintf(buf, "%d\n", trigger);
+}
+
+static ssize_t
+trigger_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct crashlog_entry *entry;
+ bool trigger;
+ int result;
+
+ entry = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &trigger);
+ if (result)
+ return result;
+
+ mutex_lock(&entry->control_mutex);
+
+ if (!trigger) {
+ pmt_crashlog_set_clear(&entry->entry);
+ } else if (pmt_crashlog_complete(&entry->entry)) {
+ /* we cannot trigger a new crash if one is still pending */
+ result = -EEXIST;
+ goto err;
+ } else if (pmt_crashlog_disabled(&entry->entry)) {
+ /* if device is currently disabled, return busy */
+ result = -EBUSY;
+ goto err;
+ } else {
+ pmt_crashlog_set_execute(&entry->entry);
+ }
+
+ result = count;
+err:
+ mutex_unlock(&entry->control_mutex);
+ return result;
+}
+static DEVICE_ATTR_RW(trigger);
+
+static struct attribute *pmt_crashlog_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_trigger.attr,
+ NULL
+};
+
+static const struct attribute_group pmt_crashlog_group = {
+ .attrs = pmt_crashlog_attrs,
+};
+
+static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev)
+{
+ void __iomem *disc_table = entry->disc_table;
+ struct crashlog_entry *crashlog;
+
+ if (!pmt_crashlog_supported(entry))
+ return 1;
+
+ /* initialize control mutex */
+ crashlog = container_of(entry, struct crashlog_entry, entry);
+ mutex_init(&crashlog->control_mutex);
+
+ header->access_type = GET_ACCESS(readl(disc_table));
+ header->guid = readl(disc_table + GUID_OFFSET);
+ header->base_offset = readl(disc_table + BASE_OFFSET);
+
+ /* Size is measured in DWORDS, but accessor returns bytes */
+ header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
+
+ return 0;
+}
+
+static DEFINE_XARRAY_ALLOC(crashlog_array);
+static struct intel_pmt_namespace pmt_crashlog_ns = {
+ .name = "crashlog",
+ .xa = &crashlog_array,
+ .attr_grp = &pmt_crashlog_group,
+ .pmt_header_decode = pmt_crashlog_header_decode,
+};
+
+/*
+ * initialization
+ */
+static int pmt_crashlog_remove(struct platform_device *pdev)
+{
+ struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < priv->num_entries; i++)
+ intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
+
+ return 0;
+}
+
+static int pmt_crashlog_probe(struct platform_device *pdev)
+{
+ struct pmt_crashlog_priv *priv;
+ size_t size;
+ int i, ret;
+
+ size = struct_size(priv, entry, pdev->num_resources);
+ priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ for (i = 0; i < pdev->num_resources; i++) {
+ struct intel_pmt_entry *entry = &priv->entry[i].entry;
+
+ ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i);
+ if (ret < 0)
+ goto abort_probe;
+ if (ret)
+ continue;
+
+ priv->num_entries++;
+ }
+
+ return 0;
+abort_probe:
+ pmt_crashlog_remove(pdev);
+ return ret;
+}
+
+static struct platform_driver pmt_crashlog_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .remove = pmt_crashlog_remove,
+ .probe = pmt_crashlog_probe,
+};
+
+static int __init pmt_crashlog_init(void)
+{
+ return platform_driver_register(&pmt_crashlog_driver);
+}
+
+static void __exit pmt_crashlog_exit(void)
+{
+ platform_driver_unregister(&pmt_crashlog_driver);
+ xa_destroy(&crashlog_array);
+}
+
+module_init(pmt_crashlog_init);
+module_exit(pmt_crashlog_exit);
+
+MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Crashlog driver");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c
new file mode 100644
index 000000000000..38d52651c572
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/telemetry.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Telemetry driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "David E. Box" <david.e.box@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/overflow.h>
+
+#include "class.h"
+
+#define TELEM_DEV_NAME "pmt_telemetry"
+
+#define TELEM_SIZE_OFFSET 0x0
+#define TELEM_GUID_OFFSET 0x4
+#define TELEM_BASE_OFFSET 0x8
+#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0))
+/* size is in bytes */
+#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10)
+
+/* Used by client hardware to identify a fixed telemetry entry*/
+#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000
+
+struct pmt_telem_priv {
+ int num_entries;
+ struct intel_pmt_entry entry[];
+};
+
+static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
+ struct device *dev)
+{
+ u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
+
+ if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID)
+ return false;
+
+ return intel_pmt_is_early_client_hw(dev);
+}
+
+static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
+ struct intel_pmt_header *header,
+ struct device *dev)
+{
+ void __iomem *disc_table = entry->disc_table;
+
+ if (pmt_telem_region_overlaps(entry, dev))
+ return 1;
+
+ header->access_type = TELEM_ACCESS(readl(disc_table));
+ header->guid = readl(disc_table + TELEM_GUID_OFFSET);
+ header->base_offset = readl(disc_table + TELEM_BASE_OFFSET);
+
+ /* Size is measured in DWORDS, but accessor returns bytes */
+ header->size = TELEM_SIZE(readl(disc_table));
+
+ /*
+ * Some devices may expose non-functioning entries that are
+ * reserved for future use. They have zero size. Do not fail
+ * probe for these. Just ignore them.
+ */
+ if (header->size == 0)
+ return 1;
+
+ return 0;
+}
+
+static DEFINE_XARRAY_ALLOC(telem_array);
+static struct intel_pmt_namespace pmt_telem_ns = {
+ .name = "telem",
+ .xa = &telem_array,
+ .pmt_header_decode = pmt_telem_header_decode,
+};
+
+static int pmt_telem_remove(struct platform_device *pdev)
+{
+ struct pmt_telem_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < priv->num_entries; i++)
+ intel_pmt_dev_destroy(&priv->entry[i], &pmt_telem_ns);
+
+ return 0;
+}
+
+static int pmt_telem_probe(struct platform_device *pdev)
+{
+ struct pmt_telem_priv *priv;
+ size_t size;
+ int i, ret;
+
+ size = struct_size(priv, entry, pdev->num_resources);
+ priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ for (i = 0; i < pdev->num_resources; i++) {
+ struct intel_pmt_entry *entry = &priv->entry[i];
+
+ ret = intel_pmt_dev_create(entry, &pmt_telem_ns, pdev, i);
+ if (ret < 0)
+ goto abort_probe;
+ if (ret)
+ continue;
+
+ priv->num_entries++;
+ }
+
+ return 0;
+abort_probe:
+ pmt_telem_remove(pdev);
+ return ret;
+}
+
+static struct platform_driver pmt_telem_driver = {
+ .driver = {
+ .name = TELEM_DEV_NAME,
+ },
+ .remove = pmt_telem_remove,
+ .probe = pmt_telem_probe,
+};
+
+static int __init pmt_telem_init(void)
+{
+ return platform_driver_register(&pmt_telem_driver);
+}
+module_init(pmt_telem_init);
+
+static void __exit pmt_telem_exit(void)
+{
+ platform_driver_unregister(&pmt_telem_driver);
+ xa_destroy(&telem_array);
+}
+module_exit(pmt_telem_exit);
+
+MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Telemetry driver");
+MODULE_ALIAS("platform:" TELEM_DEV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c
new file mode 100644
index 000000000000..f58b8543f6ac
--- /dev/null
+++ b/drivers/platform/x86/intel/punit_ipc.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Intel P-Unit Mailbox IPC mechanism
+ *
+ * (C) Copyright 2015 Intel Corporation
+ *
+ * The heart of the P-Unit is the Foxton microcontroller and its firmware,
+ * which provide mailbox interface for power management usage.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/intel_punit_ipc.h>
+
+/* IPC Mailbox registers */
+#define OFFSET_DATA_LOW 0x0
+#define OFFSET_DATA_HIGH 0x4
+/* bit field of interface register */
+#define CMD_RUN BIT(31)
+#define CMD_ERRCODE_MASK GENMASK(7, 0)
+#define CMD_PARA1_SHIFT 8
+#define CMD_PARA2_SHIFT 16
+
+#define CMD_TIMEOUT_SECONDS 1
+
+enum {
+ BASE_DATA = 0,
+ BASE_IFACE,
+ BASE_MAX,
+};
+
+typedef struct {
+ struct device *dev;
+ struct mutex lock;
+ int irq;
+ struct completion cmd_complete;
+ /* base of interface and data registers */
+ void __iomem *base[RESERVED_IPC][BASE_MAX];
+ IPC_TYPE type;
+} IPC_DEV;
+
+static IPC_DEV *punit_ipcdev;
+
+static inline u32 ipc_read_status(IPC_DEV *ipcdev, IPC_TYPE type)
+{
+ return readl(ipcdev->base[type][BASE_IFACE]);
+}
+
+static inline void ipc_write_cmd(IPC_DEV *ipcdev, IPC_TYPE type, u32 cmd)
+{
+ writel(cmd, ipcdev->base[type][BASE_IFACE]);
+}
+
+static inline u32 ipc_read_data_low(IPC_DEV *ipcdev, IPC_TYPE type)
+{
+ return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
+}
+
+static inline u32 ipc_read_data_high(IPC_DEV *ipcdev, IPC_TYPE type)
+{
+ return readl(ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
+}
+
+static inline void ipc_write_data_low(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
+{
+ writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_LOW);
+}
+
+static inline void ipc_write_data_high(IPC_DEV *ipcdev, IPC_TYPE type, u32 data)
+{
+ writel(data, ipcdev->base[type][BASE_DATA] + OFFSET_DATA_HIGH);
+}
+
+static const char *ipc_err_string(int error)
+{
+ if (error == IPC_PUNIT_ERR_SUCCESS)
+ return "no error";
+ else if (error == IPC_PUNIT_ERR_INVALID_CMD)
+ return "invalid command";
+ else if (error == IPC_PUNIT_ERR_INVALID_PARAMETER)
+ return "invalid parameter";
+ else if (error == IPC_PUNIT_ERR_CMD_TIMEOUT)
+ return "command timeout";
+ else if (error == IPC_PUNIT_ERR_CMD_LOCKED)
+ return "command locked";
+ else if (error == IPC_PUNIT_ERR_INVALID_VR_ID)
+ return "invalid vr id";
+ else if (error == IPC_PUNIT_ERR_VR_ERR)
+ return "vr error";
+ else
+ return "unknown error";
+}
+
+static int intel_punit_ipc_check_status(IPC_DEV *ipcdev, IPC_TYPE type)
+{
+ int loops = CMD_TIMEOUT_SECONDS * USEC_PER_SEC;
+ int errcode;
+ int status;
+
+ if (ipcdev->irq) {
+ if (!wait_for_completion_timeout(&ipcdev->cmd_complete,
+ CMD_TIMEOUT_SECONDS * HZ)) {
+ dev_err(ipcdev->dev, "IPC timed out\n");
+ return -ETIMEDOUT;
+ }
+ } else {
+ while ((ipc_read_status(ipcdev, type) & CMD_RUN) && --loops)
+ udelay(1);
+ if (!loops) {
+ dev_err(ipcdev->dev, "IPC timed out\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ status = ipc_read_status(ipcdev, type);
+ errcode = status & CMD_ERRCODE_MASK;
+ if (errcode) {
+ dev_err(ipcdev->dev, "IPC failed: %s, IPC_STS=0x%x\n",
+ ipc_err_string(errcode), status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * intel_punit_ipc_simple_command() - Simple IPC command
+ * @cmd: IPC command code.
+ * @para1: First 8bit parameter, set 0 if not used.
+ * @para2: Second 8bit parameter, set 0 if not used.
+ *
+ * Send a IPC command to P-Unit when there is no data transaction
+ *
+ * Return: IPC error code or 0 on success.
+ */
+int intel_punit_ipc_simple_command(int cmd, int para1, int para2)
+{
+ IPC_DEV *ipcdev = punit_ipcdev;
+ IPC_TYPE type;
+ u32 val;
+ int ret;
+
+ mutex_lock(&ipcdev->lock);
+
+ reinit_completion(&ipcdev->cmd_complete);
+ type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
+
+ val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
+ val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
+ ipc_write_cmd(ipcdev, type, val);
+ ret = intel_punit_ipc_check_status(ipcdev, type);
+
+ mutex_unlock(&ipcdev->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(intel_punit_ipc_simple_command);
+
+/**
+ * intel_punit_ipc_command() - IPC command with data and pointers
+ * @cmd: IPC command code.
+ * @para1: First 8bit parameter, set 0 if not used.
+ * @para2: Second 8bit parameter, set 0 if not used.
+ * @in: Input data, 32bit for BIOS cmd, two 32bit for GTD and ISPD.
+ * @out: Output data.
+ *
+ * Send a IPC command to P-Unit with data transaction
+ *
+ * Return: IPC error code or 0 on success.
+ */
+int intel_punit_ipc_command(u32 cmd, u32 para1, u32 para2, u32 *in, u32 *out)
+{
+ IPC_DEV *ipcdev = punit_ipcdev;
+ IPC_TYPE type;
+ u32 val;
+ int ret;
+
+ mutex_lock(&ipcdev->lock);
+
+ reinit_completion(&ipcdev->cmd_complete);
+ type = (cmd & IPC_PUNIT_CMD_TYPE_MASK) >> IPC_TYPE_OFFSET;
+
+ if (in) {
+ ipc_write_data_low(ipcdev, type, *in);
+ if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
+ ipc_write_data_high(ipcdev, type, *++in);
+ }
+
+ val = cmd & ~IPC_PUNIT_CMD_TYPE_MASK;
+ val |= CMD_RUN | para2 << CMD_PARA2_SHIFT | para1 << CMD_PARA1_SHIFT;
+ ipc_write_cmd(ipcdev, type, val);
+
+ ret = intel_punit_ipc_check_status(ipcdev, type);
+ if (ret)
+ goto out;
+
+ if (out) {
+ *out = ipc_read_data_low(ipcdev, type);
+ if (type == GTDRIVER_IPC || type == ISPDRIVER_IPC)
+ *++out = ipc_read_data_high(ipcdev, type);
+ }
+
+out:
+ mutex_unlock(&ipcdev->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(intel_punit_ipc_command);
+
+static irqreturn_t intel_punit_ioc(int irq, void *dev_id)
+{
+ IPC_DEV *ipcdev = dev_id;
+
+ complete(&ipcdev->cmd_complete);
+ return IRQ_HANDLED;
+}
+
+static int intel_punit_get_bars(struct platform_device *pdev)
+{
+ void __iomem *addr;
+
+ /*
+ * The following resources are required
+ * - BIOS_IPC BASE_DATA
+ * - BIOS_IPC BASE_IFACE
+ */
+ addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(addr))
+ return PTR_ERR(addr);
+ punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr;
+
+ addr = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(addr))
+ return PTR_ERR(addr);
+ punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr;
+
+ /*
+ * The following resources are optional
+ * - ISPDRIVER_IPC BASE_DATA
+ * - ISPDRIVER_IPC BASE_IFACE
+ * - GTDRIVER_IPC BASE_DATA
+ * - GTDRIVER_IPC BASE_IFACE
+ */
+ addr = devm_platform_ioremap_resource(pdev, 2);
+ if (!IS_ERR(addr))
+ punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr;
+
+ addr = devm_platform_ioremap_resource(pdev, 3);
+ if (!IS_ERR(addr))
+ punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr;
+
+ addr = devm_platform_ioremap_resource(pdev, 4);
+ if (!IS_ERR(addr))
+ punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr;
+
+ addr = devm_platform_ioremap_resource(pdev, 5);
+ if (!IS_ERR(addr))
+ punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr;
+
+ return 0;
+}
+
+static int intel_punit_ipc_probe(struct platform_device *pdev)
+{
+ int irq, ret;
+
+ punit_ipcdev = devm_kzalloc(&pdev->dev,
+ sizeof(*punit_ipcdev), GFP_KERNEL);
+ if (!punit_ipcdev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, punit_ipcdev);
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq < 0) {
+ dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n");
+ } else {
+ ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
+ IRQF_NO_SUSPEND, "intel_punit_ipc",
+ &punit_ipcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
+ return ret;
+ }
+ punit_ipcdev->irq = irq;
+ }
+
+ ret = intel_punit_get_bars(pdev);
+ if (ret)
+ return ret;
+
+ punit_ipcdev->dev = &pdev->dev;
+ mutex_init(&punit_ipcdev->lock);
+ init_completion(&punit_ipcdev->cmd_complete);
+
+ return 0;
+}
+
+static int intel_punit_ipc_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct acpi_device_id punit_ipc_acpi_ids[] = {
+ { "INT34D4", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, punit_ipc_acpi_ids);
+
+static struct platform_driver intel_punit_ipc_driver = {
+ .probe = intel_punit_ipc_probe,
+ .remove = intel_punit_ipc_remove,
+ .driver = {
+ .name = "intel_punit_ipc",
+ .acpi_match_table = ACPI_PTR(punit_ipc_acpi_ids),
+ },
+};
+
+static int __init intel_punit_ipc_init(void)
+{
+ return platform_driver_register(&intel_punit_ipc_driver);
+}
+
+static void __exit intel_punit_ipc_exit(void)
+{
+ platform_driver_unregister(&intel_punit_ipc_driver);
+}
+
+MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
+MODULE_DESCRIPTION("Intel P-Unit IPC driver");
+MODULE_LICENSE("GPL v2");
+
+/* Some modules are dependent on this, so init earlier */
+fs_initcall(intel_punit_ipc_init);
+module_exit(intel_punit_ipc_exit);
diff --git a/drivers/platform/x86/intel/rst.c b/drivers/platform/x86/intel/rst.c
new file mode 100644
index 000000000000..3b81cb896fed
--- /dev/null
+++ b/drivers/platform/x86/intel/rst.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+MODULE_LICENSE("GPL");
+
+static ssize_t irst_show_wakeup_events(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi;
+ unsigned long long value;
+ acpi_status status;
+
+ acpi = to_acpi_device(dev);
+
+ status = acpi_evaluate_integer(acpi->handle, "GFFS", NULL, &value);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t irst_store_wakeup_events(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi;
+ acpi_status status;
+ unsigned long value;
+ int error;
+
+ acpi = to_acpi_device(dev);
+
+ error = kstrtoul(buf, 0, &value);
+ if (error)
+ return error;
+
+ status = acpi_execute_simple_method(acpi->handle, "SFFS", value);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return count;
+}
+
+static struct device_attribute irst_wakeup_attr = {
+ .attr = { .name = "wakeup_events", .mode = 0600 },
+ .show = irst_show_wakeup_events,
+ .store = irst_store_wakeup_events
+};
+
+static ssize_t irst_show_wakeup_time(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpi_device *acpi;
+ unsigned long long value;
+ acpi_status status;
+
+ acpi = to_acpi_device(dev);
+
+ status = acpi_evaluate_integer(acpi->handle, "GFTV", NULL, &value);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t irst_store_wakeup_time(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi;
+ acpi_status status;
+ unsigned long value;
+ int error;
+
+ acpi = to_acpi_device(dev);
+
+ error = kstrtoul(buf, 0, &value);
+ if (error)
+ return error;
+
+ status = acpi_execute_simple_method(acpi->handle, "SFTV", value);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ return count;
+}
+
+static struct device_attribute irst_timeout_attr = {
+ .attr = { .name = "wakeup_time", .mode = 0600 },
+ .show = irst_show_wakeup_time,
+ .store = irst_store_wakeup_time
+};
+
+static int irst_add(struct acpi_device *acpi)
+{
+ int error;
+
+ error = device_create_file(&acpi->dev, &irst_timeout_attr);
+ if (unlikely(error))
+ return error;
+
+ error = device_create_file(&acpi->dev, &irst_wakeup_attr);
+ if (unlikely(error))
+ device_remove_file(&acpi->dev, &irst_timeout_attr);
+
+ return error;
+}
+
+static int irst_remove(struct acpi_device *acpi)
+{
+ device_remove_file(&acpi->dev, &irst_wakeup_attr);
+ device_remove_file(&acpi->dev, &irst_timeout_attr);
+
+ return 0;
+}
+
+static const struct acpi_device_id irst_ids[] = {
+ {"INT3392", 0},
+ {"", 0}
+};
+
+static struct acpi_driver irst_driver = {
+ .owner = THIS_MODULE,
+ .name = "intel_rapid_start",
+ .class = "intel_rapid_start",
+ .ids = irst_ids,
+ .ops = {
+ .add = irst_add,
+ .remove = irst_remove,
+ },
+};
+
+module_acpi_driver(irst_driver);
+
+MODULE_DEVICE_TABLE(acpi, irst_ids);
diff --git a/drivers/platform/x86/intel/smartconnect.c b/drivers/platform/x86/intel/smartconnect.c
new file mode 100644
index 000000000000..64c2dc93472f
--- /dev/null
+++ b/drivers/platform/x86/intel/smartconnect.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2013 Matthew Garrett <mjg59@srcf.ucam.org>
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+
+MODULE_LICENSE("GPL");
+
+static int smartconnect_acpi_init(struct acpi_device *acpi)
+{
+ unsigned long long value;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(acpi->handle, "GAOS", NULL, &value);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+
+ if (value & 0x1) {
+ dev_info(&acpi->dev, "Disabling Intel Smart Connect\n");
+ status = acpi_execute_simple_method(acpi->handle, "SAOS", 0);
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id smartconnect_ids[] = {
+ {"INT33A0", 0},
+ {"", 0}
+};
+MODULE_DEVICE_TABLE(acpi, smartconnect_ids);
+
+static struct acpi_driver smartconnect_driver = {
+ .owner = THIS_MODULE,
+ .name = "intel_smart_connect",
+ .class = "intel_smart_connect",
+ .ids = smartconnect_ids,
+ .ops = {
+ .add = smartconnect_acpi_init,
+ },
+};
+
+module_acpi_driver(smartconnect_driver);
diff --git a/drivers/platform/x86/intel/speed_select_if/Kconfig b/drivers/platform/x86/intel/speed_select_if/Kconfig
new file mode 100644
index 000000000000..ce3e3dc076d2
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/Kconfig
@@ -0,0 +1,17 @@
+menu "Intel Speed Select Technology interface support"
+ depends on PCI
+ depends on X86_64 || COMPILE_TEST
+
+config INTEL_SPEED_SELECT_INTERFACE
+ tristate "Intel(R) Speed Select Technology interface drivers"
+ help
+ This config enables the Intel(R) Speed Select Technology interface
+ drivers. The Intel(R) speed select technology features are non
+ architectural and only supported on specific Xeon(R) servers.
+ These drivers provide interface to directly communicate with hardware
+ via MMIO and Mail boxes to enumerate and control all the speed select
+ features.
+
+ Enable this config, if there is a need to enable and control the
+ Intel(R) Speed Select Technology features from the user space.
+endmenu
diff --git a/drivers/platform/x86/intel/speed_select_if/Makefile b/drivers/platform/x86/intel/speed_select_if/Makefile
new file mode 100644
index 000000000000..856076206f35
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile - Intel Speed Select Interface drivers
+# Copyright (c) 2019, Intel Corporation.
+#
+
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
new file mode 100644
index 000000000000..c9a85eb2e860
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
@@ -0,0 +1,741 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Common functions
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpufeature.h>
+#include <linux/cpuhotplug.h>
+#include <linux/fs.h>
+#include <linux/hashtable.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_if_common.h"
+
+#define MSR_THREAD_ID_INFO 0x53
+#define MSR_CPU_BUS_NUMBER 0x128
+
+static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
+
+static int punit_msr_white_list[] = {
+ MSR_TURBO_RATIO_LIMIT,
+ MSR_CONFIG_TDP_CONTROL,
+ MSR_TURBO_RATIO_LIMIT1,
+ MSR_TURBO_RATIO_LIMIT2,
+};
+
+struct isst_valid_cmd_ranges {
+ u16 cmd;
+ u16 sub_cmd_beg;
+ u16 sub_cmd_end;
+};
+
+struct isst_cmd_set_req_type {
+ u16 cmd;
+ u16 sub_cmd;
+ u16 param;
+};
+
+static const struct isst_valid_cmd_ranges isst_valid_cmds[] = {
+ {0xD0, 0x00, 0x03},
+ {0x7F, 0x00, 0x0B},
+ {0x7F, 0x10, 0x12},
+ {0x7F, 0x20, 0x23},
+ {0x94, 0x03, 0x03},
+ {0x95, 0x03, 0x03},
+};
+
+static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = {
+ {0xD0, 0x00, 0x08},
+ {0xD0, 0x01, 0x08},
+ {0xD0, 0x02, 0x08},
+ {0xD0, 0x03, 0x08},
+ {0x7F, 0x02, 0x00},
+ {0x7F, 0x08, 0x00},
+ {0x95, 0x03, 0x03},
+};
+
+struct isst_cmd {
+ struct hlist_node hnode;
+ u64 data;
+ u32 cmd;
+ int cpu;
+ int mbox_cmd_type;
+ u32 param;
+};
+
+static DECLARE_HASHTABLE(isst_hash, 8);
+static DEFINE_MUTEX(isst_hash_lock);
+
+static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
+ u32 data)
+{
+ struct isst_cmd *sst_cmd;
+
+ sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
+ if (!sst_cmd)
+ return -ENOMEM;
+
+ sst_cmd->cpu = cpu;
+ sst_cmd->cmd = cmd;
+ sst_cmd->mbox_cmd_type = mbox_cmd_type;
+ sst_cmd->param = param;
+ sst_cmd->data = data;
+
+ hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd);
+
+ return 0;
+}
+
+static void isst_delete_hash(void)
+{
+ struct isst_cmd *sst_cmd;
+ struct hlist_node *tmp;
+ int i;
+
+ hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) {
+ hash_del(&sst_cmd->hnode);
+ kfree(sst_cmd);
+ }
+}
+
+/**
+ * isst_store_cmd() - Store command to a hash table
+ * @cmd: Mailbox command.
+ * @sub_cmd: Mailbox sub-command or MSR id.
+ * @mbox_cmd_type: Mailbox or MSR command.
+ * @param: Mailbox parameter.
+ * @data: Mailbox request data or MSR data.
+ *
+ * Stores the command to a hash table if there is no such command already
+ * stored. If already stored update the latest parameter and data for the
+ * command.
+ *
+ * Return: Return result of store to hash table, 0 for success, others for
+ * failure.
+ */
+int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type,
+ u32 param, u64 data)
+{
+ struct isst_cmd *sst_cmd;
+ int full_cmd, ret;
+
+ full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16;
+ full_cmd |= (sub_cmd & GENMASK_ULL(15, 0));
+ mutex_lock(&isst_hash_lock);
+ hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) {
+ if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu &&
+ sst_cmd->mbox_cmd_type == mbox_cmd_type) {
+ sst_cmd->param = param;
+ sst_cmd->data = data;
+ mutex_unlock(&isst_hash_lock);
+ return 0;
+ }
+ }
+
+ ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data);
+ mutex_unlock(&isst_hash_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(isst_store_cmd);
+
+static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb,
+ struct isst_cmd *sst_cmd)
+{
+ struct isst_if_mbox_cmd mbox_cmd;
+ int wr_only;
+
+ mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16;
+ mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0);
+ mbox_cmd.parameter = sst_cmd->param;
+ mbox_cmd.req_data = sst_cmd->data;
+ mbox_cmd.logical_cpu = sst_cmd->cpu;
+ (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1);
+}
+
+/**
+ * isst_resume_common() - Process Resume request
+ *
+ * On resume replay all mailbox commands and MSRs.
+ *
+ * Return: None.
+ */
+void isst_resume_common(void)
+{
+ struct isst_cmd *sst_cmd;
+ int i;
+
+ hash_for_each(isst_hash, i, sst_cmd, hnode) {
+ struct isst_if_cmd_cb *cb;
+
+ if (sst_cmd->mbox_cmd_type) {
+ cb = &punit_callbacks[ISST_IF_DEV_MBOX];
+ if (cb->registered)
+ isst_mbox_resume_command(cb, sst_cmd);
+ } else {
+ wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
+ sst_cmd->data);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(isst_resume_common);
+
+static void isst_restore_msr_local(int cpu)
+{
+ struct isst_cmd *sst_cmd;
+ int i;
+
+ mutex_lock(&isst_hash_lock);
+ for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
+ if (!punit_msr_white_list[i])
+ break;
+
+ hash_for_each_possible(isst_hash, sst_cmd, hnode,
+ punit_msr_white_list[i]) {
+ if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
+ wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
+ }
+ }
+ mutex_unlock(&isst_hash_lock);
+}
+
+/**
+ * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands
+ * @cmd: Pointer to the command structure to verify.
+ *
+ * Invalid command to PUNIT to may result in instability of the platform.
+ * This function has a whitelist of commands, which are allowed.
+ *
+ * Return: Return true if the command is invalid, else false.
+ */
+bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd)
+{
+ int i;
+
+ if (cmd->logical_cpu >= nr_cpu_ids)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) {
+ if (cmd->command == isst_valid_cmds[i].cmd &&
+ (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg &&
+ cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid);
+
+/**
+ * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request
+ * @cmd: Pointer to the command structure to verify.
+ *
+ * Check if the given mail box level is set request and not a get request.
+ *
+ * Return: Return true if the command is set_req, else false.
+ */
+bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) {
+ if (cmd->command == isst_cmd_set_reqs[i].cmd &&
+ cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd &&
+ cmd->parameter == isst_cmd_set_reqs[i].param) {
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
+
+static int isst_if_get_platform_info(void __user *argp)
+{
+ struct isst_if_platform_info info;
+
+ info.api_version = ISST_IF_API_VERSION;
+ info.driver_version = ISST_IF_DRIVER_VERSION;
+ info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT;
+ info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
+ info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered;
+
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+struct isst_if_cpu_info {
+ /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
+ int bus_info[2];
+ struct pci_dev *pci_dev[2];
+ int punit_cpu_id;
+ int numa_node;
+};
+
+static struct isst_if_cpu_info *isst_cpu_info;
+#define ISST_MAX_PCI_DOMAINS 8
+
+static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
+{
+ struct pci_dev *matched_pci_dev = NULL;
+ struct pci_dev *pci_dev = NULL;
+ int no_matches = 0;
+ int i, bus_number;
+
+ if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
+ cpu >= num_possible_cpus())
+ return NULL;
+
+ bus_number = isst_cpu_info[cpu].bus_info[bus_no];
+ if (bus_number < 0)
+ return NULL;
+
+ for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) {
+ struct pci_dev *_pci_dev;
+ int node;
+
+ _pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn));
+ if (!_pci_dev)
+ continue;
+
+ ++no_matches;
+ if (!matched_pci_dev)
+ matched_pci_dev = _pci_dev;
+
+ node = dev_to_node(&_pci_dev->dev);
+ if (node == NUMA_NO_NODE) {
+ pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n",
+ cpu, bus_no, dev, fn);
+ continue;
+ }
+
+ if (node == isst_cpu_info[cpu].numa_node) {
+ pci_dev = _pci_dev;
+ break;
+ }
+ }
+
+ /*
+ * If there is no numa matched pci_dev, then there can be following cases:
+ * 1. CONFIG_NUMA is not defined: In this case if there is only single device
+ * match, then we don't need numa information. Simply return last match.
+ * Othewise return NULL.
+ * 2. NUMA information is not exposed via _SEG method. In this case it is similar
+ * to case 1.
+ * 3. Numa information doesn't match with CPU numa node and more than one match
+ * return NULL.
+ */
+ if (!pci_dev && no_matches == 1)
+ pci_dev = matched_pci_dev;
+
+ return pci_dev;
+}
+
+/**
+ * isst_if_get_pci_dev() - Get the PCI device instance for a CPU
+ * @cpu: Logical CPU number.
+ * @bus_number: The bus number assigned by the hardware.
+ * @dev: The device number assigned by the hardware.
+ * @fn: The function number assigned by the hardware.
+ *
+ * Using cached bus information, find out the PCI device for a bus number,
+ * device and function.
+ *
+ * Return: Return pci_dev pointer or NULL.
+ */
+struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
+{
+ struct pci_dev *pci_dev;
+
+ if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
+ cpu >= num_possible_cpus())
+ return NULL;
+
+ pci_dev = isst_cpu_info[cpu].pci_dev[bus_no];
+
+ if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn))
+ return pci_dev;
+
+ return _isst_if_get_pci_dev(cpu, bus_no, dev, fn);
+}
+EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
+
+static int isst_if_cpu_online(unsigned int cpu)
+{
+ u64 data;
+ int ret;
+
+ isst_cpu_info[cpu].numa_node = cpu_to_node(cpu);
+
+ ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
+ if (ret) {
+ /* This is not a fatal error on MSR mailbox only I/F */
+ isst_cpu_info[cpu].bus_info[0] = -1;
+ isst_cpu_info[cpu].bus_info[1] = -1;
+ } else {
+ isst_cpu_info[cpu].bus_info[0] = data & 0xff;
+ isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
+ isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1);
+ isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1);
+ }
+
+ ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
+ if (ret) {
+ isst_cpu_info[cpu].punit_cpu_id = -1;
+ return ret;
+ }
+ isst_cpu_info[cpu].punit_cpu_id = data;
+
+ isst_restore_msr_local(cpu);
+
+ return 0;
+}
+
+static int isst_if_online_id;
+
+static int isst_if_cpu_info_init(void)
+{
+ int ret;
+
+ isst_cpu_info = kcalloc(num_possible_cpus(),
+ sizeof(*isst_cpu_info),
+ GFP_KERNEL);
+ if (!isst_cpu_info)
+ return -ENOMEM;
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "platform/x86/isst-if:online",
+ isst_if_cpu_online, NULL);
+ if (ret < 0) {
+ kfree(isst_cpu_info);
+ return ret;
+ }
+
+ isst_if_online_id = ret;
+
+ return 0;
+}
+
+static void isst_if_cpu_info_exit(void)
+{
+ cpuhp_remove_state(isst_if_online_id);
+ kfree(isst_cpu_info);
+};
+
+static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_cpu_map *cpu_map;
+
+ cpu_map = (struct isst_if_cpu_map *)cmd_ptr;
+ if (cpu_map->logical_cpu >= nr_cpu_ids ||
+ cpu_map->logical_cpu >= num_possible_cpus())
+ return -EINVAL;
+
+ *write_only = 0;
+ cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id;
+
+ return 0;
+}
+
+static bool match_punit_msr_white_list(int msr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
+ if (punit_msr_white_list[i] == msr)
+ return true;
+ }
+
+ return false;
+}
+
+static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_msr_cmd *msr_cmd;
+ int ret;
+
+ msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr;
+
+ if (!match_punit_msr_white_list(msr_cmd->msr))
+ return -EINVAL;
+
+ if (msr_cmd->logical_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ if (msr_cmd->read_write) {
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ msr_cmd->msr,
+ msr_cmd->data);
+ *write_only = 1;
+ if (!ret && !resume)
+ ret = isst_store_cmd(0, msr_cmd->msr,
+ msr_cmd->logical_cpu,
+ 0, 0, msr_cmd->data);
+ } else {
+ u64 data;
+
+ ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ msr_cmd->msr, &data);
+ if (!ret) {
+ msr_cmd->data = data;
+ *write_only = 0;
+ }
+ }
+
+
+ return ret;
+}
+
+static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb)
+{
+ unsigned char __user *ptr;
+ u32 cmd_count;
+ u8 *cmd_ptr;
+ long ret;
+ int i;
+
+ /* Each multi command has u32 command count as the first field */
+ if (copy_from_user(&cmd_count, argp, sizeof(cmd_count)))
+ return -EFAULT;
+
+ if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT)
+ return -EINVAL;
+
+ cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL);
+ if (!cmd_ptr)
+ return -ENOMEM;
+
+ /* cb->offset points to start of the command after the command count */
+ ptr = argp + cb->offset;
+
+ for (i = 0; i < cmd_count; ++i) {
+ int wr_only;
+
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ ret = cb->cmd_callback(cmd_ptr, &wr_only, 0);
+ if (ret)
+ break;
+
+ if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ ptr += cb->cmd_size;
+ }
+
+ kfree(cmd_ptr);
+
+ return i ? i : ret;
+}
+
+static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct isst_if_cmd_cb cmd_cb;
+ struct isst_if_cmd_cb *cb;
+ long ret = -ENOTTY;
+
+ switch (cmd) {
+ case ISST_IF_GET_PLATFORM_INFO:
+ ret = isst_if_get_platform_info(argp);
+ break;
+ case ISST_IF_GET_PHY_ID:
+ cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map);
+ cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map);
+ cmd_cb.cmd_callback = isst_if_proc_phyid_req;
+ ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
+ break;
+ case ISST_IF_IO_CMD:
+ cb = &punit_callbacks[ISST_IF_DEV_MMIO];
+ if (cb->registered)
+ ret = isst_if_exec_multi_cmd(argp, cb);
+ break;
+ case ISST_IF_MBOX_COMMAND:
+ cb = &punit_callbacks[ISST_IF_DEV_MBOX];
+ if (cb->registered)
+ ret = isst_if_exec_multi_cmd(argp, cb);
+ break;
+ case ISST_IF_MSR_COMMAND:
+ cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd);
+ cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd);
+ cmd_cb.cmd_callback = isst_if_msr_cmd_req;
+ ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static DEFINE_MUTEX(punit_misc_dev_lock);
+static int misc_usage_count;
+static int misc_device_ret;
+static int misc_device_open;
+
+static int isst_if_open(struct inode *inode, struct file *file)
+{
+ int i, ret = 0;
+
+ /* Fail open, if a module is going away */
+ mutex_lock(&punit_misc_dev_lock);
+ for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
+ struct isst_if_cmd_cb *cb = &punit_callbacks[i];
+
+ if (cb->registered && !try_module_get(cb->owner)) {
+ ret = -ENODEV;
+ break;
+ }
+ }
+ if (ret) {
+ int j;
+
+ for (j = 0; j < i; ++j) {
+ struct isst_if_cmd_cb *cb;
+
+ cb = &punit_callbacks[j];
+ if (cb->registered)
+ module_put(cb->owner);
+ }
+ } else {
+ misc_device_open++;
+ }
+ mutex_unlock(&punit_misc_dev_lock);
+
+ return ret;
+}
+
+static int isst_if_relase(struct inode *inode, struct file *f)
+{
+ int i;
+
+ mutex_lock(&punit_misc_dev_lock);
+ misc_device_open--;
+ for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
+ struct isst_if_cmd_cb *cb = &punit_callbacks[i];
+
+ if (cb->registered)
+ module_put(cb->owner);
+ }
+ mutex_unlock(&punit_misc_dev_lock);
+
+ return 0;
+}
+
+static const struct file_operations isst_if_char_driver_ops = {
+ .open = isst_if_open,
+ .unlocked_ioctl = isst_if_def_ioctl,
+ .release = isst_if_relase,
+};
+
+static struct miscdevice isst_if_char_driver = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "isst_interface",
+ .fops = &isst_if_char_driver_ops,
+};
+
+/**
+ * isst_if_cdev_register() - Register callback for IOCTL
+ * @device_type: The device type this callback handling.
+ * @cb: Callback structure.
+ *
+ * This function registers a callback to device type. On very first call
+ * it will register a misc device, which is used for user kernel interface.
+ * Other calls simply increment ref count. Registry will fail, if the user
+ * already opened misc device for operation. Also if the misc device
+ * creation failed, then it will not try again and all callers will get
+ * failure code.
+ *
+ * Return: Return the return value from the misc creation device or -EINVAL
+ * for unsupported device type.
+ */
+int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
+{
+ if (misc_device_ret)
+ return misc_device_ret;
+
+ if (device_type >= ISST_IF_DEV_MAX)
+ return -EINVAL;
+
+ mutex_lock(&punit_misc_dev_lock);
+ if (misc_device_open) {
+ mutex_unlock(&punit_misc_dev_lock);
+ return -EAGAIN;
+ }
+ if (!misc_usage_count) {
+ int ret;
+
+ misc_device_ret = misc_register(&isst_if_char_driver);
+ if (misc_device_ret)
+ goto unlock_exit;
+
+ ret = isst_if_cpu_info_init();
+ if (ret) {
+ misc_deregister(&isst_if_char_driver);
+ misc_device_ret = ret;
+ goto unlock_exit;
+ }
+ }
+ memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
+ punit_callbacks[device_type].registered = 1;
+ misc_usage_count++;
+unlock_exit:
+ mutex_unlock(&punit_misc_dev_lock);
+
+ return misc_device_ret;
+}
+EXPORT_SYMBOL_GPL(isst_if_cdev_register);
+
+/**
+ * isst_if_cdev_unregister() - Unregister callback for IOCTL
+ * @device_type: The device type to unregister.
+ *
+ * This function unregisters the previously registered callback. If this
+ * is the last callback unregistering, then misc device is removed.
+ *
+ * Return: None.
+ */
+void isst_if_cdev_unregister(int device_type)
+{
+ mutex_lock(&punit_misc_dev_lock);
+ misc_usage_count--;
+ punit_callbacks[device_type].registered = 0;
+ if (device_type == ISST_IF_DEV_MBOX)
+ isst_delete_hash();
+ if (!misc_usage_count && !misc_device_ret) {
+ misc_deregister(&isst_if_char_driver);
+ isst_if_cpu_info_exit();
+ }
+ mutex_unlock(&punit_misc_dev_lock);
+}
+EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.h b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h
new file mode 100644
index 000000000000..fdecdae248d7
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel Speed Select Interface: Drivers Internal defines
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#ifndef __ISST_IF_COMMON_H
+#define __ISST_IF_COMMON_H
+
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459
+
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259
+
+/*
+ * Validate maximum commands in a single request.
+ * This is enough to handle command to every core in one ioctl, or all
+ * possible message id to one CPU. Limit is also helpful for resonse time
+ * per IOCTL request, as PUNIT may take different times to process each
+ * request and may hold for long for too many commands.
+ */
+#define ISST_IF_CMD_LIMIT 64
+
+#define ISST_IF_API_VERSION 0x01
+#define ISST_IF_DRIVER_VERSION 0x01
+
+#define ISST_IF_DEV_MBOX 0
+#define ISST_IF_DEV_MMIO 1
+#define ISST_IF_DEV_MAX 2
+
+/**
+ * struct isst_if_cmd_cb - Used to register a IOCTL handler
+ * @registered: Used by the common code to store registry. Caller don't
+ * to touch this field
+ * @cmd_size: The command size of the individual command in IOCTL
+ * @offset: Offset to the first valid member in command structure.
+ * This will be the offset of the start of the command
+ * after command count field
+ * @cmd_callback: Callback function to handle IOCTL. The callback has the
+ * command pointer with data for command. There is a pointer
+ * called write_only, which when set, will not copy the
+ * response to user ioctl buffer. The "resume" argument
+ * can be used to avoid storing the command for replay
+ * during system resume
+ *
+ * This structure is used to register an handler for IOCTL. To avoid
+ * code duplication common code handles all the IOCTL command read/write
+ * including handling multiple command in single IOCTL. The caller just
+ * need to execute a command via the registered callback.
+ */
+struct isst_if_cmd_cb {
+ int registered;
+ int cmd_size;
+ int offset;
+ struct module *owner;
+ long (*cmd_callback)(u8 *ptr, int *write_only, int resume);
+};
+
+/* Internal interface functions */
+int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb);
+void isst_if_cdev_unregister(int type);
+struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn);
+bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd);
+bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd);
+int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd,
+ u32 param, u64 data);
+void isst_resume_common(void);
+#endif
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
new file mode 100644
index 000000000000..1b6eab071068
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Mbox via MSR Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/cpuhotplug.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/topology.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#include "isst_if_common.h"
+
+#define MSR_OS_MAILBOX_INTERFACE 0xB0
+#define MSR_OS_MAILBOX_DATA 0xB1
+#define MSR_OS_MAILBOX_BUSY_BIT 31
+
+/*
+ * Based on experiments count is never more than 1, as the MSR overhead
+ * is enough to finish the command. So here this is the worst case number.
+ */
+#define OS_MAILBOX_RETRY_COUNT 3
+
+static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
+ u32 command_data, u32 *response_data)
+{
+ u32 retries;
+ u64 data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ retries = OS_MAILBOX_RETRY_COUNT;
+ do {
+ rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ if (ret)
+ return ret;
+
+ /* Write DATA register */
+ wrmsrl(MSR_OS_MAILBOX_DATA, command_data);
+
+ /* Write command register */
+ data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) |
+ (parameter & GENMASK_ULL(13, 0)) << 16 |
+ (sub_command << 8) |
+ command;
+ wrmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+
+ /* Poll for rb bit == 0 */
+ retries = OS_MAILBOX_RETRY_COUNT;
+ do {
+ rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+
+ if (data & 0xff)
+ return -ENXIO;
+
+ if (response_data) {
+ rdmsrl(MSR_OS_MAILBOX_DATA, data);
+ *response_data = data;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ return ret;
+}
+
+struct msrl_action {
+ int err;
+ struct isst_if_mbox_cmd *mbox_cmd;
+};
+
+/* revisit, smp_call_function_single should be enough for atomic mailbox! */
+static void msrl_update_func(void *info)
+{
+ struct msrl_action *act = info;
+
+ act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command,
+ act->mbox_cmd->sub_command,
+ act->mbox_cmd->parameter,
+ act->mbox_cmd->req_data,
+ &act->mbox_cmd->resp_data);
+}
+
+static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct msrl_action action;
+ int ret;
+
+ action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
+
+ if (isst_if_mbox_cmd_invalid(action.mbox_cmd))
+ return -EINVAL;
+
+ if (isst_if_mbox_cmd_set_req(action.mbox_cmd) &&
+ !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /*
+ * To complete mailbox command, we need to access two MSRs.
+ * So we don't want race to complete a mailbox transcation.
+ * Here smp_call ensures that msrl_update_func() has no race
+ * and also with wait flag, wait for completion.
+ * smp_call_function_single is using get_cpu() and put_cpu().
+ */
+ ret = smp_call_function_single(action.mbox_cmd->logical_cpu,
+ msrl_update_func, &action, 1);
+ if (ret)
+ return ret;
+
+ if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd))
+ action.err = isst_store_cmd(action.mbox_cmd->command,
+ action.mbox_cmd->sub_command,
+ action.mbox_cmd->logical_cpu, 1,
+ action.mbox_cmd->parameter,
+ action.mbox_cmd->req_data);
+ *write_only = 0;
+
+ return action.err;
+}
+
+
+static int isst_pm_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
+ switch (mode) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ isst_resume_common();
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block isst_pm_nb = {
+ .notifier_call = isst_pm_notify,
+};
+
+static const struct x86_cpu_id isst_if_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids);
+
+static int __init isst_if_mbox_init(void)
+{
+ struct isst_if_cmd_cb cb;
+ const struct x86_cpu_id *id;
+ u64 data;
+ int ret;
+
+ id = x86_match_cpu(isst_if_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ /* Check presence of mailbox MSRs */
+ ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data);
+ if (ret)
+ return ret;
+
+ ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data);
+ if (ret)
+ return ret;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
+ cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
+ cb.cmd_callback = isst_if_mbox_proc_cmd;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
+ if (ret)
+ return ret;
+
+ ret = register_pm_notifier(&isst_pm_nb);
+ if (ret)
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+
+ return ret;
+}
+module_init(isst_if_mbox_init)
+
+static void __exit isst_if_mbox_exit(void)
+{
+ unregister_pm_notifier(&isst_pm_nb);
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+}
+module_exit(isst_if_mbox_exit)
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface mailbox driver");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c
new file mode 100644
index 000000000000..df1fc6c719f3
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Mbox via PCI Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpufeature.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_if_common.h"
+
+#define PUNIT_MAILBOX_DATA 0xA0
+#define PUNIT_MAILBOX_INTERFACE 0xA4
+#define PUNIT_MAILBOX_BUSY_BIT 31
+
+/*
+ * The average time to complete mailbox commands is less than 40us. Most of
+ * the commands complete in few micro seconds. But the same firmware handles
+ * requests from all power management features.
+ * We can create a scenario where we flood the firmware with requests then
+ * the mailbox response can be delayed for 100s of micro seconds. So define
+ * two timeouts. One for average case and one for long.
+ * If the firmware is taking more than average, just call cond_resched().
+ */
+#define OS_MAILBOX_TIMEOUT_AVG_US 40
+#define OS_MAILBOX_TIMEOUT_MAX_US 1000
+
+struct isst_if_device {
+ struct mutex mutex;
+};
+
+static int isst_if_mbox_cmd(struct pci_dev *pdev,
+ struct isst_if_mbox_cmd *mbox_cmd)
+{
+ s64 tm_delta = 0;
+ ktime_t tm;
+ u32 data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ tm = ktime_get();
+ do {
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
+ &data);
+ if (ret)
+ return ret;
+
+ if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
+
+ if (ret)
+ return ret;
+
+ /* Write DATA register */
+ ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA,
+ mbox_cmd->req_data);
+ if (ret)
+ return ret;
+
+ /* Write command register */
+ data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) |
+ (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 |
+ (mbox_cmd->sub_command << 8) |
+ mbox_cmd->command;
+
+ ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data);
+ if (ret)
+ return ret;
+
+ /* Poll for rb bit == 0 */
+ tm_delta = 0;
+ tm = ktime_get();
+ do {
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
+ &data);
+ if (ret)
+ return ret;
+
+ if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
+ continue;
+ }
+
+ if (data & 0xff)
+ return -ENXIO;
+
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data);
+ if (ret)
+ return ret;
+
+ mbox_cmd->resp_data = data;
+ ret = 0;
+ break;
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
+
+ return ret;
+}
+
+static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_mbox_cmd *mbox_cmd;
+ struct isst_if_device *punit_dev;
+ struct pci_dev *pdev;
+ int ret;
+
+ mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
+
+ if (isst_if_mbox_cmd_invalid(mbox_cmd))
+ return -EINVAL;
+
+ if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1);
+ if (!pdev)
+ return -EINVAL;
+
+ punit_dev = pci_get_drvdata(pdev);
+ if (!punit_dev)
+ return -EINVAL;
+
+ /*
+ * Basically we are allowing one complete mailbox transaction on
+ * a mapped PCI device at a time.
+ */
+ mutex_lock(&punit_dev->mutex);
+ ret = isst_if_mbox_cmd(pdev, mbox_cmd);
+ if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd))
+ ret = isst_store_cmd(mbox_cmd->command,
+ mbox_cmd->sub_command,
+ mbox_cmd->logical_cpu, 1,
+ mbox_cmd->parameter,
+ mbox_cmd->req_data);
+ mutex_unlock(&punit_dev->mutex);
+ if (ret)
+ return ret;
+
+ *write_only = 0;
+
+ return 0;
+}
+
+static const struct pci_device_id isst_if_mbox_ids[] = {
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)},
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)},
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids);
+
+static int isst_if_mbox_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_cmd_cb cb;
+ int ret;
+
+ punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
+ if (!punit_dev)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ mutex_init(&punit_dev->mutex);
+ pci_set_drvdata(pdev, punit_dev);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
+ cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
+ cb.cmd_callback = isst_if_mbox_proc_cmd;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
+
+ if (ret)
+ mutex_destroy(&punit_dev->mutex);
+
+ return ret;
+}
+
+static void isst_if_mbox_remove(struct pci_dev *pdev)
+{
+ struct isst_if_device *punit_dev;
+
+ punit_dev = pci_get_drvdata(pdev);
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+ mutex_destroy(&punit_dev->mutex);
+}
+
+static int __maybe_unused isst_if_resume(struct device *device)
+{
+ isst_resume_common();
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume);
+
+static struct pci_driver isst_if_pci_driver = {
+ .name = "isst_if_mbox_pci",
+ .id_table = isst_if_mbox_ids,
+ .probe = isst_if_mbox_probe,
+ .remove = isst_if_mbox_remove,
+ .driver.pm = &isst_if_pm_ops,
+};
+
+module_pci_driver(isst_if_pci_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
new file mode 100644
index 000000000000..ff49025ec085
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: MMIO Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_if_common.h"
+
+struct isst_mmio_range {
+ int beg;
+ int end;
+};
+
+static struct isst_mmio_range mmio_range_devid_0[] = {
+ {0x04, 0x14},
+ {0x20, 0xD0},
+};
+
+static struct isst_mmio_range mmio_range_devid_1[] = {
+ {0x04, 0x14},
+ {0x20, 0x11C},
+};
+
+struct isst_if_device {
+ void __iomem *punit_mmio;
+ u32 range_0[5];
+ u32 range_1[64];
+ struct isst_mmio_range *mmio_range;
+ struct mutex mutex;
+};
+
+static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_io_reg *io_reg;
+ struct pci_dev *pdev;
+
+ io_reg = (struct isst_if_io_reg *)cmd_ptr;
+
+ if (io_reg->reg % 4)
+ return -EINVAL;
+
+ if (io_reg->read_write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1);
+ if (!pdev)
+ return -EINVAL;
+
+ punit_dev = pci_get_drvdata(pdev);
+ if (!punit_dev)
+ return -EINVAL;
+
+ if (io_reg->reg < punit_dev->mmio_range[0].beg ||
+ io_reg->reg > punit_dev->mmio_range[1].end)
+ return -EINVAL;
+
+ /*
+ * Ensure that operation is complete on a PCI device to avoid read
+ * write race by using per PCI device mutex.
+ */
+ mutex_lock(&punit_dev->mutex);
+ if (io_reg->read_write) {
+ writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg);
+ *write_only = 1;
+ } else {
+ io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg);
+ *write_only = 0;
+ }
+ mutex_unlock(&punit_dev->mutex);
+
+ return 0;
+}
+
+static const struct pci_device_id isst_if_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)},
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)},
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, isst_if_ids);
+
+static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_cmd_cb cb;
+ u32 mmio_base, pcu_base;
+ u64 base_addr;
+ int ret;
+
+ punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
+ if (!punit_dev)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ ret = pci_read_config_dword(pdev, 0xD0, &mmio_base);
+ if (ret)
+ return ret;
+
+ ret = pci_read_config_dword(pdev, 0xFC, &pcu_base);
+ if (ret)
+ return ret;
+
+ pcu_base &= GENMASK(10, 0);
+ base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12;
+ punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256);
+ if (!punit_dev->punit_mmio)
+ return -ENOMEM;
+
+ mutex_init(&punit_dev->mutex);
+ pci_set_drvdata(pdev, punit_dev);
+ punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_io_reg);
+ cb.offset = offsetof(struct isst_if_io_regs, io_reg);
+ cb.cmd_callback = isst_if_mmio_rd_wr;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb);
+ if (ret)
+ mutex_destroy(&punit_dev->mutex);
+
+ return ret;
+}
+
+static void isst_if_remove(struct pci_dev *pdev)
+{
+ struct isst_if_device *punit_dev;
+
+ punit_dev = pci_get_drvdata(pdev);
+ isst_if_cdev_unregister(ISST_IF_DEV_MMIO);
+ mutex_destroy(&punit_dev->mutex);
+}
+
+static int __maybe_unused isst_if_suspend(struct device *device)
+{
+ struct isst_if_device *punit_dev = dev_get_drvdata(device);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
+ punit_dev->range_0[i] = readl(punit_dev->punit_mmio +
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+ punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused isst_if_resume(struct device *device)
+{
+ struct isst_if_device *punit_dev = dev_get_drvdata(device);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
+ writel(punit_dev->range_0[i], punit_dev->punit_mmio +
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+
+ writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume);
+
+static struct pci_driver isst_if_pci_driver = {
+ .name = "isst_if_pci",
+ .id_table = isst_if_ids,
+ .probe = isst_if_probe,
+ .remove = isst_if_remove,
+ .driver.pm = &isst_if_pm_ops,
+};
+
+module_pci_driver(isst_if_pci_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface mmio driver");
diff --git a/drivers/platform/x86/intel/telemetry/Kconfig b/drivers/platform/x86/intel/telemetry/Kconfig
new file mode 100644
index 000000000000..da887bd03731
--- /dev/null
+++ b/drivers/platform/x86/intel/telemetry/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+config INTEL_TELEMETRY
+ tristate "Intel SoC Telemetry driver"
+ depends on X86_64
+ depends on MFD_INTEL_PMC_BXT
+ depends on INTEL_PUNIT_IPC
+ help
+ This driver provides interfaces to configure and use
+ telemetry for Intel SoC from Apollo Lake onwards.
+ It is also used to get various SoC events and parameters
+ directly via debugfs files. Various tools may use
+ this interface for SoC state monitoring.
diff --git a/drivers/platform/x86/intel/telemetry/Makefile b/drivers/platform/x86/intel/telemetry/Makefile
new file mode 100644
index 000000000000..bfdba5b6c59a
--- /dev/null
+++ b/drivers/platform/x86/intel/telemetry/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+intel_telemetry_core-y := core.o
+obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o
+intel_telemetry_pltdrv-y := pltdrv.o
+obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_pltdrv.o
+intel_telemetry_debugfs-y := debugfs.o
+obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_debugfs.o
diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c
new file mode 100644
index 000000000000..fdf55b5d6948
--- /dev/null
+++ b/drivers/platform/x86/intel/telemetry/core.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel SoC Core Telemetry Driver
+ * Copyright (C) 2015, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Telemetry Framework provides platform related PM and performance statistics.
+ * This file provides the core telemetry API implementation.
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <asm/intel_telemetry.h>
+
+#define DRIVER_NAME "intel_telemetry_core"
+
+struct telemetry_core_config {
+ struct telemetry_plt_config *plt_config;
+ const struct telemetry_core_ops *telem_ops;
+};
+
+static struct telemetry_core_config telm_core_conf;
+
+static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig,
+ struct telemetry_evtconfig ioss_evtconfig)
+{
+ return 0;
+}
+
+static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period)
+{
+ return 0;
+}
+
+static int telemetry_def_get_sampling_period(u8 *pss_min_period,
+ u8 *pss_max_period,
+ u8 *ioss_min_period,
+ u8 *ioss_max_period)
+{
+ return 0;
+}
+
+static int telemetry_def_get_eventconfig(
+ struct telemetry_evtconfig *pss_evtconfig,
+ struct telemetry_evtconfig *ioss_evtconfig,
+ int pss_len, int ioss_len)
+{
+ return 0;
+}
+
+static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
+ u32 *verbosity)
+{
+ return 0;
+}
+
+
+static int telemetry_def_set_trace_verbosity(enum telemetry_unit telem_unit,
+ u32 verbosity)
+{
+ return 0;
+}
+
+static int telemetry_def_raw_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog,
+ int len, int log_all_evts)
+{
+ return 0;
+}
+
+static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog,
+ int len, int log_all_evts)
+{
+ return 0;
+}
+
+static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts,
+ u32 *pss_evtmap, u32 *ioss_evtmap)
+{
+ return 0;
+}
+
+static int telemetry_def_reset_events(void)
+{
+ return 0;
+}
+
+static const struct telemetry_core_ops telm_defpltops = {
+ .set_sampling_period = telemetry_def_set_sampling_period,
+ .get_sampling_period = telemetry_def_get_sampling_period,
+ .get_trace_verbosity = telemetry_def_get_trace_verbosity,
+ .set_trace_verbosity = telemetry_def_set_trace_verbosity,
+ .raw_read_eventlog = telemetry_def_raw_read_eventlog,
+ .get_eventconfig = telemetry_def_get_eventconfig,
+ .read_eventlog = telemetry_def_read_eventlog,
+ .update_events = telemetry_def_update_events,
+ .reset_events = telemetry_def_reset_events,
+ .add_events = telemetry_def_add_events,
+};
+
+/**
+ * telemetry_update_events() - Update telemetry Configuration
+ * @pss_evtconfig: PSS related config. No change if num_evts = 0.
+ * @pss_evtconfig: IOSS related config. No change if num_evts = 0.
+ *
+ * This API updates the IOSS & PSS Telemetry configuration. Old config
+ * is overwritten. Call telemetry_reset_events when logging is over
+ * All sample period values should be in the form of:
+ * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
+ struct telemetry_evtconfig ioss_evtconfig)
+{
+ return telm_core_conf.telem_ops->update_events(pss_evtconfig,
+ ioss_evtconfig);
+}
+EXPORT_SYMBOL_GPL(telemetry_update_events);
+
+
+/**
+ * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period
+ * @pss_period: placeholder for PSS Period to be set.
+ * Set to 0 if not required to be updated
+ * @ioss_period: placeholder for IOSS Period to be set
+ * Set to 0 if not required to be updated
+ *
+ * All values should be in the form of:
+ * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period)
+{
+ return telm_core_conf.telem_ops->set_sampling_period(pss_period,
+ ioss_period);
+}
+EXPORT_SYMBOL_GPL(telemetry_set_sampling_period);
+
+/**
+ * telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period
+ * @pss_min_period: placeholder for PSS Min Period supported
+ * @pss_max_period: placeholder for PSS Max Period supported
+ * @ioss_min_period: placeholder for IOSS Min Period supported
+ * @ioss_max_period: placeholder for IOSS Max Period supported
+ *
+ * All values should be in the form of:
+ * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
+ u8 *ioss_min_period, u8 *ioss_max_period)
+{
+ return telm_core_conf.telem_ops->get_sampling_period(pss_min_period,
+ pss_max_period,
+ ioss_min_period,
+ ioss_max_period);
+}
+EXPORT_SYMBOL_GPL(telemetry_get_sampling_period);
+
+
+/**
+ * telemetry_reset_events() - Restore the IOSS & PSS configuration to default
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_reset_events(void)
+{
+ return telm_core_conf.telem_ops->reset_events();
+}
+EXPORT_SYMBOL_GPL(telemetry_reset_events);
+
+/**
+ * telemetry_get_eventconfig() - Returns the pss and ioss events enabled
+ * @pss_evtconfig: Pointer to PSS related configuration.
+ * @pss_evtconfig: Pointer to IOSS related configuration.
+ * @pss_len: Number of u32 elements allocated for pss_evtconfig array
+ * @ioss_len: Number of u32 elements allocated for ioss_evtconfig array
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig,
+ struct telemetry_evtconfig *ioss_evtconfig,
+ int pss_len, int ioss_len)
+{
+ return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig,
+ ioss_evtconfig,
+ pss_len, ioss_len);
+}
+EXPORT_SYMBOL_GPL(telemetry_get_eventconfig);
+
+/**
+ * telemetry_add_events() - Add IOSS & PSS configuration to existing settings.
+ * @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0.
+ * @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0.
+ * @pss_evtmap: Array of PSS Event-IDs to Enable
+ * @ioss_evtmap: Array of PSS Event-IDs to Enable
+ *
+ * Events are appended to Old Configuration. In case of total events > 28, it
+ * returns error. Call telemetry_reset_events to reset after eventlog done
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
+ u32 *pss_evtmap, u32 *ioss_evtmap)
+{
+ return telm_core_conf.telem_ops->add_events(num_pss_evts,
+ num_ioss_evts, pss_evtmap,
+ ioss_evtmap);
+}
+EXPORT_SYMBOL_GPL(telemetry_add_events);
+
+/**
+ * telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @evtlog: Array of telemetry_evtlog structs to fill data
+ * evtlog.telem_evt_id specifies the ids to read
+ * @len: Length of array of evtlog
+ *
+ * Return: number of eventlogs read for success, < 0 for failure
+ */
+int telemetry_read_events(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog, int len)
+{
+ return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog,
+ len, 0);
+}
+EXPORT_SYMBOL_GPL(telemetry_read_events);
+
+/**
+ * telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @evtlog: Array of telemetry_evtlog structs to fill data
+ * evtlog.telem_evt_id specifies the ids to read
+ * @len: Length of array of evtlog
+ *
+ * The caller must take care of locking in this case.
+ *
+ * Return: number of eventlogs read for success, < 0 for failure
+ */
+int telemetry_raw_read_events(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog, int len)
+{
+ return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
+ len, 0);
+}
+EXPORT_SYMBOL_GPL(telemetry_raw_read_events);
+
+/**
+ * telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @evtlog: Array of telemetry_evtlog structs to fill data
+ * @len: Length of array of evtlog
+ *
+ * Return: number of eventlogs read for success, < 0 for failure
+ */
+int telemetry_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog, int len)
+{
+ return telm_core_conf.telem_ops->read_eventlog(telem_unit, evtlog,
+ len, 1);
+}
+EXPORT_SYMBOL_GPL(telemetry_read_eventlog);
+
+/**
+ * telemetry_raw_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @evtlog: Array of telemetry_evtlog structs to fill data
+ * @len: Length of array of evtlog
+ *
+ * The caller must take care of locking in this case.
+ *
+ * Return: number of eventlogs read for success, < 0 for failure
+ */
+int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog, int len)
+{
+ return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
+ len, 1);
+}
+EXPORT_SYMBOL_GPL(telemetry_raw_read_eventlog);
+
+
+/**
+ * telemetry_get_trace_verbosity() - Get the IOSS & PSS Trace verbosity
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @verbosity: Pointer to return Verbosity
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_get_trace_verbosity(enum telemetry_unit telem_unit,
+ u32 *verbosity)
+{
+ return telm_core_conf.telem_ops->get_trace_verbosity(telem_unit,
+ verbosity);
+}
+EXPORT_SYMBOL_GPL(telemetry_get_trace_verbosity);
+
+
+/**
+ * telemetry_set_trace_verbosity() - Update the IOSS & PSS Trace verbosity
+ * @telem_unit: Specify whether IOSS or PSS Read
+ * @verbosity: Verbosity to set
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit, u32 verbosity)
+{
+ return telm_core_conf.telem_ops->set_trace_verbosity(telem_unit,
+ verbosity);
+}
+EXPORT_SYMBOL_GPL(telemetry_set_trace_verbosity);
+
+/**
+ * telemetry_set_pltdata() - Set the platform specific Data
+ * @ops: Pointer to ops structure
+ * @pltconfig: Platform config data
+ *
+ * Usage by other than telemetry pltdrv module is invalid
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_set_pltdata(const struct telemetry_core_ops *ops,
+ struct telemetry_plt_config *pltconfig)
+{
+ if (ops)
+ telm_core_conf.telem_ops = ops;
+
+ if (pltconfig)
+ telm_core_conf.plt_config = pltconfig;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(telemetry_set_pltdata);
+
+/**
+ * telemetry_clear_pltdata() - Clear the platform specific Data
+ *
+ * Usage by other than telemetry pltdrv module is invalid
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_clear_pltdata(void)
+{
+ telm_core_conf.telem_ops = &telm_defpltops;
+ telm_core_conf.plt_config = NULL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(telemetry_clear_pltdata);
+
+/**
+ * telemetry_get_pltdata() - Return telemetry platform config
+ *
+ * May be used by other telemetry modules to get platform specific
+ * configuration.
+ */
+struct telemetry_plt_config *telemetry_get_pltdata(void)
+{
+ return telm_core_conf.plt_config;
+}
+EXPORT_SYMBOL_GPL(telemetry_get_pltdata);
+
+static inline int telemetry_get_pssevtname(enum telemetry_unit telem_unit,
+ const char **name, int len)
+{
+ struct telemetry_unit_config psscfg;
+ int i;
+
+ if (!telm_core_conf.plt_config)
+ return -EINVAL;
+
+ psscfg = telm_core_conf.plt_config->pss_config;
+
+ if (len > psscfg.ssram_evts_used)
+ len = psscfg.ssram_evts_used;
+
+ for (i = 0; i < len; i++)
+ name[i] = psscfg.telem_evts[i].name;
+
+ return 0;
+}
+
+static inline int telemetry_get_iossevtname(enum telemetry_unit telem_unit,
+ const char **name, int len)
+{
+ struct telemetry_unit_config iosscfg;
+ int i;
+
+ if (!(telm_core_conf.plt_config))
+ return -EINVAL;
+
+ iosscfg = telm_core_conf.plt_config->ioss_config;
+
+ if (len > iosscfg.ssram_evts_used)
+ len = iosscfg.ssram_evts_used;
+
+ for (i = 0; i < len; i++)
+ name[i] = iosscfg.telem_evts[i].name;
+
+ return 0;
+
+}
+
+/**
+ * telemetry_get_evtname() - Checkif platform config is valid
+ * @telem_unit: Telemetry Unit to check
+ * @name: Array of character pointers to contain name
+ * @len: length of array name provided by user
+ *
+ * Usage by other than telemetry debugfs module is invalid
+ *
+ * Return: 0 success, < 0 for failure
+ */
+int telemetry_get_evtname(enum telemetry_unit telem_unit,
+ const char **name, int len)
+{
+ int ret = -EINVAL;
+
+ if (telem_unit == TELEM_PSS)
+ ret = telemetry_get_pssevtname(telem_unit, name, len);
+
+ else if (telem_unit == TELEM_IOSS)
+ ret = telemetry_get_iossevtname(telem_unit, name, len);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(telemetry_get_evtname);
+
+static int __init telemetry_module_init(void)
+{
+ pr_info(pr_fmt(DRIVER_NAME) " Init\n");
+
+ telm_core_conf.telem_ops = &telm_defpltops;
+ return 0;
+}
+
+static void __exit telemetry_module_exit(void)
+{
+}
+
+module_init(telemetry_module_init);
+module_exit(telemetry_module_exit);
+
+MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
+MODULE_DESCRIPTION("Intel SoC Telemetry Interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/telemetry/debugfs.c b/drivers/platform/x86/intel/telemetry/debugfs.c
new file mode 100644
index 000000000000..1d4d0fbfd63c
--- /dev/null
+++ b/drivers/platform/x86/intel/telemetry/debugfs.c
@@ -0,0 +1,961 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel SOC Telemetry debugfs Driver: Currently supports APL
+ * Copyright (c) 2015, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * This file provides the debugfs interfaces for telemetry.
+ * /sys/kernel/debug/telemetry/pss_info: Shows Primary Control Sub-Sys Counters
+ * /sys/kernel/debug/telemetry/ioss_info: Shows IO Sub-System Counters
+ * /sys/kernel/debug/telemetry/soc_states: Shows SoC State
+ * /sys/kernel/debug/telemetry/pss_trace_verbosity: Read and Change Tracing
+ * Verbosity via firmware
+ * /sys/kernel/debug/telemetry/ioss_race_verbosity: Write and Change Tracing
+ * Verbosity via firmware
+ */
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/mfd/intel_pmc_bxt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/seq_file.h>
+#include <linux/suspend.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+#include <asm/intel_telemetry.h>
+
+#define DRIVER_NAME "telemetry_soc_debugfs"
+#define DRIVER_VERSION "1.0.0"
+
+/* ApolloLake SoC Event-IDs */
+#define TELEM_APL_PSS_PSTATES_ID 0x2802
+#define TELEM_APL_PSS_IDLE_ID 0x2806
+#define TELEM_APL_PCS_IDLE_BLOCKED_ID 0x2C00
+#define TELEM_APL_PCS_S0IX_BLOCKED_ID 0x2C01
+#define TELEM_APL_PSS_WAKEUP_ID 0x2C02
+#define TELEM_APL_PSS_LTR_BLOCKING_ID 0x2C03
+
+#define TELEM_APL_S0IX_TOTAL_OCC_ID 0x4000
+#define TELEM_APL_S0IX_SHLW_OCC_ID 0x4001
+#define TELEM_APL_S0IX_DEEP_OCC_ID 0x4002
+#define TELEM_APL_S0IX_TOTAL_RES_ID 0x4800
+#define TELEM_APL_S0IX_SHLW_RES_ID 0x4801
+#define TELEM_APL_S0IX_DEEP_RES_ID 0x4802
+#define TELEM_APL_D0IX_ID 0x581A
+#define TELEM_APL_D3_ID 0x5819
+#define TELEM_APL_PG_ID 0x5818
+
+#define TELEM_INFO_SRAMEVTS_MASK 0xFF00
+#define TELEM_INFO_SRAMEVTS_SHIFT 0x8
+#define TELEM_SSRAM_READ_TIMEOUT 10
+
+#define TELEM_MASK_BIT 1
+#define TELEM_MASK_BYTE 0xFF
+#define BYTES_PER_LONG 8
+#define TELEM_APL_MASK_PCS_STATE 0xF
+
+/* Max events in bitmap to check for */
+#define TELEM_PSS_IDLE_EVTS 25
+#define TELEM_PSS_IDLE_BLOCKED_EVTS 20
+#define TELEM_PSS_S0IX_BLOCKED_EVTS 20
+#define TELEM_PSS_S0IX_WAKEUP_EVTS 20
+#define TELEM_PSS_LTR_BLOCKING_EVTS 20
+#define TELEM_IOSS_DX_D0IX_EVTS 25
+#define TELEM_IOSS_PG_EVTS 30
+
+#define TELEM_CHECK_AND_PARSE_EVTS(EVTID, EVTNUM, BUF, EVTLOG, EVTDAT, MASK) { \
+ if (evtlog[index].telem_evtid == (EVTID)) { \
+ for (idx = 0; idx < (EVTNUM); idx++) \
+ (BUF)[idx] = ((EVTLOG) >> (EVTDAT)[idx].bit_pos) & \
+ (MASK); \
+ continue; \
+ } \
+}
+
+#define TELEM_CHECK_AND_PARSE_CTRS(EVTID, CTR) { \
+ if (evtlog[index].telem_evtid == (EVTID)) { \
+ (CTR) = evtlog[index].telem_evtlog; \
+ continue; \
+ } \
+}
+
+static u8 suspend_prep_ok;
+static u32 suspend_shlw_ctr_temp, suspend_deep_ctr_temp;
+static u64 suspend_shlw_res_temp, suspend_deep_res_temp;
+
+struct telemetry_susp_stats {
+ u32 shlw_ctr;
+ u32 deep_ctr;
+ u64 shlw_res;
+ u64 deep_res;
+};
+
+/* Bitmap definitions for default counters in APL */
+struct telem_pss_idle_stateinfo {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_pss_idle_stateinfo telem_apl_pss_idle_data[] = {
+ {"IA_CORE0_C1E", 0},
+ {"IA_CORE1_C1E", 1},
+ {"IA_CORE2_C1E", 2},
+ {"IA_CORE3_C1E", 3},
+ {"IA_CORE0_C6", 16},
+ {"IA_CORE1_C6", 17},
+ {"IA_CORE2_C6", 18},
+ {"IA_CORE3_C6", 19},
+ {"IA_MODULE0_C7", 32},
+ {"IA_MODULE1_C7", 33},
+ {"GT_RC6", 40},
+ {"IUNIT_PROCESSING_IDLE", 41},
+ {"FAR_MEM_IDLE", 43},
+ {"DISPLAY_IDLE", 44},
+ {"IUNIT_INPUT_SYSTEM_IDLE", 45},
+ {"PCS_STATUS", 60},
+};
+
+struct telem_pcs_blkd_info {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_pcs_blkd_info telem_apl_pcs_idle_blkd_data[] = {
+ {"COMPUTE", 0},
+ {"MISC", 8},
+ {"MODULE_ACTIONS_PENDING", 16},
+ {"LTR", 24},
+ {"DISPLAY_WAKE", 32},
+ {"ISP_WAKE", 40},
+ {"PSF0_ACTIVE", 48},
+};
+
+static struct telem_pcs_blkd_info telem_apl_pcs_s0ix_blkd_data[] = {
+ {"LTR", 0},
+ {"IRTL", 8},
+ {"WAKE_DEADLINE_PENDING", 16},
+ {"DISPLAY", 24},
+ {"ISP", 32},
+ {"CORE", 40},
+ {"PMC", 48},
+ {"MISC", 56},
+};
+
+struct telem_pss_ltr_info {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_pss_ltr_info telem_apl_pss_ltr_data[] = {
+ {"CORE_ACTIVE", 0},
+ {"MEM_UP", 8},
+ {"DFX", 16},
+ {"DFX_FORCE_LTR", 24},
+ {"DISPLAY", 32},
+ {"ISP", 40},
+ {"SOUTH", 48},
+};
+
+struct telem_pss_wakeup_info {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_pss_wakeup_info telem_apl_pss_wakeup[] = {
+ {"IP_IDLE", 0},
+ {"DISPLAY_WAKE", 8},
+ {"VOLTAGE_REG_INT", 16},
+ {"DROWSY_TIMER (HOTPLUG)", 24},
+ {"CORE_WAKE", 32},
+ {"MISC_S0IX", 40},
+ {"MISC_ABORT", 56},
+};
+
+struct telem_ioss_d0ix_stateinfo {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_ioss_d0ix_stateinfo telem_apl_ioss_d0ix_data[] = {
+ {"CSE", 0},
+ {"SCC2", 1},
+ {"GMM", 2},
+ {"XDCI", 3},
+ {"XHCI", 4},
+ {"ISH", 5},
+ {"AVS", 6},
+ {"PCIE0P1", 7},
+ {"PECI0P0", 8},
+ {"LPSS", 9},
+ {"SCC", 10},
+ {"PWM", 11},
+ {"PCIE1_P3", 12},
+ {"PCIE1_P2", 13},
+ {"PCIE1_P1", 14},
+ {"PCIE1_P0", 15},
+ {"CNV", 16},
+ {"SATA", 17},
+ {"PRTC", 18},
+};
+
+struct telem_ioss_pg_info {
+ const char *name;
+ u32 bit_pos;
+};
+
+static struct telem_ioss_pg_info telem_apl_ioss_pg_data[] = {
+ {"LPSS", 0},
+ {"SCC", 1},
+ {"P2SB", 2},
+ {"SCC2", 3},
+ {"GMM", 4},
+ {"PCIE0", 5},
+ {"XDCI", 6},
+ {"xHCI", 7},
+ {"CSE", 8},
+ {"SPI", 9},
+ {"AVSPGD4", 10},
+ {"AVSPGD3", 11},
+ {"AVSPGD2", 12},
+ {"AVSPGD1", 13},
+ {"ISH", 14},
+ {"EXI", 15},
+ {"NPKVRC", 16},
+ {"NPKVNN", 17},
+ {"CUNIT", 18},
+ {"FUSE_CTRL", 19},
+ {"PCIE1", 20},
+ {"CNV", 21},
+ {"LPC", 22},
+ {"SATA", 23},
+ {"SMB", 24},
+ {"PRTC", 25},
+};
+
+struct telemetry_debugfs_conf {
+ struct telemetry_susp_stats suspend_stats;
+ struct dentry *telemetry_dbg_dir;
+
+ /* Bitmap Data */
+ struct telem_ioss_d0ix_stateinfo *ioss_d0ix_data;
+ struct telem_pss_idle_stateinfo *pss_idle_data;
+ struct telem_pcs_blkd_info *pcs_idle_blkd_data;
+ struct telem_pcs_blkd_info *pcs_s0ix_blkd_data;
+ struct telem_pss_wakeup_info *pss_wakeup;
+ struct telem_pss_ltr_info *pss_ltr_data;
+ struct telem_ioss_pg_info *ioss_pg_data;
+ u8 pcs_idle_blkd_evts;
+ u8 pcs_s0ix_blkd_evts;
+ u8 pss_wakeup_evts;
+ u8 pss_idle_evts;
+ u8 pss_ltr_evts;
+ u8 ioss_d0ix_evts;
+ u8 ioss_pg_evts;
+
+ /* IDs */
+ u16 pss_ltr_blocking_id;
+ u16 pcs_idle_blkd_id;
+ u16 pcs_s0ix_blkd_id;
+ u16 s0ix_total_occ_id;
+ u16 s0ix_shlw_occ_id;
+ u16 s0ix_deep_occ_id;
+ u16 s0ix_total_res_id;
+ u16 s0ix_shlw_res_id;
+ u16 s0ix_deep_res_id;
+ u16 pss_wakeup_id;
+ u16 ioss_d0ix_id;
+ u16 pstates_id;
+ u16 pss_idle_id;
+ u16 ioss_d3_id;
+ u16 ioss_pg_id;
+};
+
+static struct telemetry_debugfs_conf *debugfs_conf;
+
+static struct telemetry_debugfs_conf telem_apl_debugfs_conf = {
+ .pss_idle_data = telem_apl_pss_idle_data,
+ .pcs_idle_blkd_data = telem_apl_pcs_idle_blkd_data,
+ .pcs_s0ix_blkd_data = telem_apl_pcs_s0ix_blkd_data,
+ .pss_ltr_data = telem_apl_pss_ltr_data,
+ .pss_wakeup = telem_apl_pss_wakeup,
+ .ioss_d0ix_data = telem_apl_ioss_d0ix_data,
+ .ioss_pg_data = telem_apl_ioss_pg_data,
+
+ .pss_idle_evts = ARRAY_SIZE(telem_apl_pss_idle_data),
+ .pcs_idle_blkd_evts = ARRAY_SIZE(telem_apl_pcs_idle_blkd_data),
+ .pcs_s0ix_blkd_evts = ARRAY_SIZE(telem_apl_pcs_s0ix_blkd_data),
+ .pss_ltr_evts = ARRAY_SIZE(telem_apl_pss_ltr_data),
+ .pss_wakeup_evts = ARRAY_SIZE(telem_apl_pss_wakeup),
+ .ioss_d0ix_evts = ARRAY_SIZE(telem_apl_ioss_d0ix_data),
+ .ioss_pg_evts = ARRAY_SIZE(telem_apl_ioss_pg_data),
+
+ .pstates_id = TELEM_APL_PSS_PSTATES_ID,
+ .pss_idle_id = TELEM_APL_PSS_IDLE_ID,
+ .pcs_idle_blkd_id = TELEM_APL_PCS_IDLE_BLOCKED_ID,
+ .pcs_s0ix_blkd_id = TELEM_APL_PCS_S0IX_BLOCKED_ID,
+ .pss_wakeup_id = TELEM_APL_PSS_WAKEUP_ID,
+ .pss_ltr_blocking_id = TELEM_APL_PSS_LTR_BLOCKING_ID,
+ .s0ix_total_occ_id = TELEM_APL_S0IX_TOTAL_OCC_ID,
+ .s0ix_shlw_occ_id = TELEM_APL_S0IX_SHLW_OCC_ID,
+ .s0ix_deep_occ_id = TELEM_APL_S0IX_DEEP_OCC_ID,
+ .s0ix_total_res_id = TELEM_APL_S0IX_TOTAL_RES_ID,
+ .s0ix_shlw_res_id = TELEM_APL_S0IX_SHLW_RES_ID,
+ .s0ix_deep_res_id = TELEM_APL_S0IX_DEEP_RES_ID,
+ .ioss_d0ix_id = TELEM_APL_D0IX_ID,
+ .ioss_d3_id = TELEM_APL_D3_ID,
+ .ioss_pg_id = TELEM_APL_PG_ID,
+};
+
+static const struct x86_cpu_id telemetry_debugfs_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_debugfs_conf),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_apl_debugfs_conf),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, telemetry_debugfs_cpu_ids);
+
+static int telemetry_debugfs_check_evts(void)
+{
+ if ((debugfs_conf->pss_idle_evts > TELEM_PSS_IDLE_EVTS) ||
+ (debugfs_conf->pcs_idle_blkd_evts > TELEM_PSS_IDLE_BLOCKED_EVTS) ||
+ (debugfs_conf->pcs_s0ix_blkd_evts > TELEM_PSS_S0IX_BLOCKED_EVTS) ||
+ (debugfs_conf->pss_ltr_evts > TELEM_PSS_LTR_BLOCKING_EVTS) ||
+ (debugfs_conf->pss_wakeup_evts > TELEM_PSS_S0IX_WAKEUP_EVTS) ||
+ (debugfs_conf->ioss_d0ix_evts > TELEM_IOSS_DX_D0IX_EVTS) ||
+ (debugfs_conf->ioss_pg_evts > TELEM_IOSS_PG_EVTS))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int telem_pss_states_show(struct seq_file *s, void *unused)
+{
+ struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ struct telemetry_debugfs_conf *conf = debugfs_conf;
+ const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ u32 pcs_idle_blkd[TELEM_PSS_IDLE_BLOCKED_EVTS],
+ pcs_s0ix_blkd[TELEM_PSS_S0IX_BLOCKED_EVTS],
+ pss_s0ix_wakeup[TELEM_PSS_S0IX_WAKEUP_EVTS],
+ pss_ltr_blkd[TELEM_PSS_LTR_BLOCKING_EVTS],
+ pss_idle[TELEM_PSS_IDLE_EVTS];
+ int index, idx, ret, err = 0;
+ u64 pstates = 0;
+
+ ret = telemetry_read_eventlog(TELEM_PSS, evtlog,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (ret < 0)
+ return ret;
+
+ err = telemetry_get_evtname(TELEM_PSS, name,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (err < 0)
+ return err;
+
+ seq_puts(s, "\n----------------------------------------------------\n");
+ seq_puts(s, "\tPSS TELEM EVENTLOG (Residency = field/19.2 us\n");
+ seq_puts(s, "----------------------------------------------------\n");
+ for (index = 0; index < ret; index++) {
+ seq_printf(s, "%-32s %llu\n",
+ name[index], evtlog[index].telem_evtlog);
+
+ /* Fetch PSS IDLE State */
+ if (evtlog[index].telem_evtid == conf->pss_idle_id) {
+ pss_idle[conf->pss_idle_evts - 1] =
+ (evtlog[index].telem_evtlog >>
+ conf->pss_idle_data[conf->pss_idle_evts - 1].bit_pos) &
+ TELEM_APL_MASK_PCS_STATE;
+ }
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->pss_idle_id,
+ conf->pss_idle_evts - 1,
+ pss_idle, evtlog[index].telem_evtlog,
+ conf->pss_idle_data, TELEM_MASK_BIT);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_idle_blkd_id,
+ conf->pcs_idle_blkd_evts,
+ pcs_idle_blkd,
+ evtlog[index].telem_evtlog,
+ conf->pcs_idle_blkd_data,
+ TELEM_MASK_BYTE);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->pcs_s0ix_blkd_id,
+ conf->pcs_s0ix_blkd_evts,
+ pcs_s0ix_blkd,
+ evtlog[index].telem_evtlog,
+ conf->pcs_s0ix_blkd_data,
+ TELEM_MASK_BYTE);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->pss_wakeup_id,
+ conf->pss_wakeup_evts,
+ pss_s0ix_wakeup,
+ evtlog[index].telem_evtlog,
+ conf->pss_wakeup, TELEM_MASK_BYTE);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->pss_ltr_blocking_id,
+ conf->pss_ltr_evts, pss_ltr_blkd,
+ evtlog[index].telem_evtlog,
+ conf->pss_ltr_data, TELEM_MASK_BYTE);
+
+ if (evtlog[index].telem_evtid == debugfs_conf->pstates_id)
+ pstates = evtlog[index].telem_evtlog;
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "PStates\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Domain\t\t\t\tFreq(Mhz)\n");
+ seq_printf(s, " IA\t\t\t\t %llu\n GT\t\t\t\t %llu\n",
+ (pstates & TELEM_MASK_BYTE)*100,
+ ((pstates >> 8) & TELEM_MASK_BYTE)*50/3);
+
+ seq_printf(s, " IUNIT\t\t\t\t %llu\n SA\t\t\t\t %llu\n",
+ ((pstates >> 16) & TELEM_MASK_BYTE)*25,
+ ((pstates >> 24) & TELEM_MASK_BYTE)*50/3);
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "PSS IDLE Status\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Device\t\t\t\t\tIDLE\n");
+ for (index = 0; index < debugfs_conf->pss_idle_evts; index++) {
+ seq_printf(s, "%-32s\t%u\n",
+ debugfs_conf->pss_idle_data[index].name,
+ pss_idle[index]);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "PSS Idle blkd Status (~1ms saturating bucket)\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Blocker\t\t\t\t\tCount\n");
+ for (index = 0; index < debugfs_conf->pcs_idle_blkd_evts; index++) {
+ seq_printf(s, "%-32s\t%u\n",
+ debugfs_conf->pcs_idle_blkd_data[index].name,
+ pcs_idle_blkd[index]);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "PSS S0ix blkd Status (~1ms saturating bucket)\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Blocker\t\t\t\t\tCount\n");
+ for (index = 0; index < debugfs_conf->pcs_s0ix_blkd_evts; index++) {
+ seq_printf(s, "%-32s\t%u\n",
+ debugfs_conf->pcs_s0ix_blkd_data[index].name,
+ pcs_s0ix_blkd[index]);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "LTR Blocking Status (~1ms saturating bucket)\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Blocker\t\t\t\t\tCount\n");
+ for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) {
+ seq_printf(s, "%-32s\t%u\n",
+ debugfs_conf->pss_ltr_data[index].name,
+ pss_s0ix_wakeup[index]);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "Wakes Status (~1ms saturating bucket)\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Wakes\t\t\t\t\tCount\n");
+ for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) {
+ seq_printf(s, "%-32s\t%u\n",
+ debugfs_conf->pss_wakeup[index].name,
+ pss_ltr_blkd[index]);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(telem_pss_states);
+
+static int telem_ioss_states_show(struct seq_file *s, void *unused)
+{
+ struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ const char *name[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ int index, ret, err;
+
+ ret = telemetry_read_eventlog(TELEM_IOSS, evtlog,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (ret < 0)
+ return ret;
+
+ err = telemetry_get_evtname(TELEM_IOSS, name,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (err < 0)
+ return err;
+
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "\tI0SS TELEMETRY EVENTLOG\n");
+ seq_puts(s, "--------------------------------------\n");
+ for (index = 0; index < ret; index++) {
+ seq_printf(s, "%-32s 0x%llx\n",
+ name[index], evtlog[index].telem_evtlog);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(telem_ioss_states);
+
+static int telem_soc_states_show(struct seq_file *s, void *unused)
+{
+ u32 d3_sts[TELEM_IOSS_DX_D0IX_EVTS], d0ix_sts[TELEM_IOSS_DX_D0IX_EVTS];
+ u32 pg_sts[TELEM_IOSS_PG_EVTS], pss_idle[TELEM_PSS_IDLE_EVTS];
+ struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ u32 s0ix_total_ctr = 0, s0ix_shlw_ctr = 0, s0ix_deep_ctr = 0;
+ u64 s0ix_total_res = 0, s0ix_shlw_res = 0, s0ix_deep_res = 0;
+ struct telemetry_debugfs_conf *conf = debugfs_conf;
+ struct pci_dev *dev = NULL;
+ int index, idx, ret;
+ u32 d3_state;
+ u16 pmcsr;
+
+ ret = telemetry_read_eventlog(TELEM_IOSS, evtlog,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (ret < 0)
+ return ret;
+
+ for (index = 0; index < ret; index++) {
+ TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d3_id,
+ conf->ioss_d0ix_evts,
+ d3_sts, evtlog[index].telem_evtlog,
+ conf->ioss_d0ix_data,
+ TELEM_MASK_BIT);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_pg_id, conf->ioss_pg_evts,
+ pg_sts, evtlog[index].telem_evtlog,
+ conf->ioss_pg_data, TELEM_MASK_BIT);
+
+ TELEM_CHECK_AND_PARSE_EVTS(conf->ioss_d0ix_id,
+ conf->ioss_d0ix_evts,
+ d0ix_sts, evtlog[index].telem_evtlog,
+ conf->ioss_d0ix_data,
+ TELEM_MASK_BIT);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_occ_id,
+ s0ix_total_ctr);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
+ s0ix_shlw_ctr);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
+ s0ix_deep_ctr);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_total_res_id,
+ s0ix_total_res);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
+ s0ix_shlw_res);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
+ s0ix_deep_res);
+ }
+
+ seq_puts(s, "\n---------------------------------------------------\n");
+ seq_puts(s, "S0IX Type\t\t\t Occurrence\t\t Residency(us)\n");
+ seq_puts(s, "---------------------------------------------------\n");
+
+ seq_printf(s, "S0IX Shallow\t\t\t %10u\t %10llu\n",
+ s0ix_shlw_ctr -
+ conf->suspend_stats.shlw_ctr,
+ (u64)((s0ix_shlw_res -
+ conf->suspend_stats.shlw_res)*10/192));
+
+ seq_printf(s, "S0IX Deep\t\t\t %10u\t %10llu\n",
+ s0ix_deep_ctr -
+ conf->suspend_stats.deep_ctr,
+ (u64)((s0ix_deep_res -
+ conf->suspend_stats.deep_res)*10/192));
+
+ seq_printf(s, "Suspend(With S0ixShallow)\t %10u\t %10llu\n",
+ conf->suspend_stats.shlw_ctr,
+ (u64)(conf->suspend_stats.shlw_res*10)/192);
+
+ seq_printf(s, "Suspend(With S0ixDeep)\t\t %10u\t %10llu\n",
+ conf->suspend_stats.deep_ctr,
+ (u64)(conf->suspend_stats.deep_res*10)/192);
+
+ seq_printf(s, "TOTAL S0IX\t\t\t %10u\t %10llu\n", s0ix_total_ctr,
+ (u64)(s0ix_total_res*10/192));
+ seq_puts(s, "\n-------------------------------------------------\n");
+ seq_puts(s, "\t\tDEVICE STATES\n");
+ seq_puts(s, "-------------------------------------------------\n");
+
+ for_each_pci_dev(dev) {
+ pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
+ d3_state = ((pmcsr & PCI_PM_CTRL_STATE_MASK) ==
+ (__force int)PCI_D3hot) ? 1 : 0;
+
+ seq_printf(s, "pci %04x %04X %s %20.20s: ",
+ dev->vendor, dev->device, dev_name(&dev->dev),
+ dev_driver_string(&dev->dev));
+ seq_printf(s, " d3:%x\n", d3_state);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "D3/D0i3 Status\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Block\t\t D3\t D0i3\n");
+ for (index = 0; index < conf->ioss_d0ix_evts; index++) {
+ seq_printf(s, "%-10s\t %u\t %u\n",
+ conf->ioss_d0ix_data[index].name,
+ d3_sts[index], d0ix_sts[index]);
+ }
+
+ seq_puts(s, "\n--------------------------------------\n");
+ seq_puts(s, "South Complex PowerGate Status\n");
+ seq_puts(s, "--------------------------------------\n");
+ seq_puts(s, "Device\t\t PG\n");
+ for (index = 0; index < conf->ioss_pg_evts; index++) {
+ seq_printf(s, "%-10s\t %u\n",
+ conf->ioss_pg_data[index].name,
+ pg_sts[index]);
+ }
+
+ evtlog->telem_evtid = conf->pss_idle_id;
+ ret = telemetry_read_events(TELEM_PSS, evtlog, 1);
+ if (ret < 0)
+ return ret;
+
+ seq_puts(s, "\n-----------------------------------------\n");
+ seq_puts(s, "North Idle Status\n");
+ seq_puts(s, "-----------------------------------------\n");
+ for (idx = 0; idx < conf->pss_idle_evts - 1; idx++) {
+ pss_idle[idx] = (evtlog->telem_evtlog >>
+ conf->pss_idle_data[idx].bit_pos) &
+ TELEM_MASK_BIT;
+ }
+
+ pss_idle[idx] = (evtlog->telem_evtlog >>
+ conf->pss_idle_data[idx].bit_pos) &
+ TELEM_APL_MASK_PCS_STATE;
+
+ for (index = 0; index < conf->pss_idle_evts; index++) {
+ seq_printf(s, "%-30s %u\n",
+ conf->pss_idle_data[index].name,
+ pss_idle[index]);
+ }
+
+ seq_puts(s, "\nPCS_STATUS Code\n");
+ seq_puts(s, "0:C0 1:C1 2:C1_DN_WT_DEV 3:C2 4:C2_WT_DE_MEM_UP\n");
+ seq_puts(s, "5:C2_WT_DE_MEM_DOWN 6:C2_UP_WT_DEV 7:C2_DN 8:C2_VOA\n");
+ seq_puts(s, "9:C2_VOA_UP 10:S0IX_PRE 11:S0IX\n");
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(telem_soc_states);
+
+static int telem_s0ix_res_get(void *data, u64 *val)
+{
+ struct telemetry_plt_config *plt_config = telemetry_get_pltdata();
+ u64 s0ix_total_res;
+ int ret;
+
+ ret = intel_pmc_s0ix_counter_read(plt_config->pmc, &s0ix_total_res);
+ if (ret) {
+ pr_err("Failed to read S0ix residency");
+ return ret;
+ }
+
+ *val = s0ix_total_res;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(telem_s0ix_fops, telem_s0ix_res_get, NULL, "%llu\n");
+
+static int telem_pss_trc_verb_show(struct seq_file *s, void *unused)
+{
+ u32 verbosity;
+ int err;
+
+ err = telemetry_get_trace_verbosity(TELEM_PSS, &verbosity);
+ if (err) {
+ pr_err("Get PSS Trace Verbosity Failed with Error %d\n", err);
+ return -EFAULT;
+ }
+
+ seq_printf(s, "PSS Trace Verbosity %u\n", verbosity);
+ return 0;
+}
+
+static ssize_t telem_pss_trc_verb_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ u32 verbosity;
+ int err;
+
+ err = kstrtou32_from_user(userbuf, count, 0, &verbosity);
+ if (err)
+ return err;
+
+ err = telemetry_set_trace_verbosity(TELEM_PSS, verbosity);
+ if (err) {
+ pr_err("Changing PSS Trace Verbosity Failed. Error %d\n", err);
+ return err;
+ }
+
+ return count;
+}
+
+static int telem_pss_trc_verb_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, telem_pss_trc_verb_show, inode->i_private);
+}
+
+static const struct file_operations telem_pss_trc_verb_ops = {
+ .open = telem_pss_trc_verb_open,
+ .read = seq_read,
+ .write = telem_pss_trc_verb_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int telem_ioss_trc_verb_show(struct seq_file *s, void *unused)
+{
+ u32 verbosity;
+ int err;
+
+ err = telemetry_get_trace_verbosity(TELEM_IOSS, &verbosity);
+ if (err) {
+ pr_err("Get IOSS Trace Verbosity Failed with Error %d\n", err);
+ return -EFAULT;
+ }
+
+ seq_printf(s, "IOSS Trace Verbosity %u\n", verbosity);
+ return 0;
+}
+
+static ssize_t telem_ioss_trc_verb_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ u32 verbosity;
+ int err;
+
+ err = kstrtou32_from_user(userbuf, count, 0, &verbosity);
+ if (err)
+ return err;
+
+ err = telemetry_set_trace_verbosity(TELEM_IOSS, verbosity);
+ if (err) {
+ pr_err("Changing IOSS Trace Verbosity Failed. Error %d\n", err);
+ return err;
+ }
+
+ return count;
+}
+
+static int telem_ioss_trc_verb_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, telem_ioss_trc_verb_show, inode->i_private);
+}
+
+static const struct file_operations telem_ioss_trc_verb_ops = {
+ .open = telem_ioss_trc_verb_open,
+ .read = seq_read,
+ .write = telem_ioss_trc_verb_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int pm_suspend_prep_cb(void)
+{
+ struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ struct telemetry_debugfs_conf *conf = debugfs_conf;
+ int ret, index;
+
+ ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (ret < 0) {
+ suspend_prep_ok = 0;
+ goto out;
+ }
+
+ for (index = 0; index < ret; index++) {
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
+ suspend_shlw_ctr_temp);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
+ suspend_deep_ctr_temp);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
+ suspend_shlw_res_temp);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
+ suspend_deep_res_temp);
+ }
+ suspend_prep_ok = 1;
+out:
+ return NOTIFY_OK;
+}
+
+static int pm_suspend_exit_cb(void)
+{
+ struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS];
+ static u32 suspend_shlw_ctr_exit, suspend_deep_ctr_exit;
+ static u64 suspend_shlw_res_exit, suspend_deep_res_exit;
+ struct telemetry_debugfs_conf *conf = debugfs_conf;
+ int ret, index;
+
+ if (!suspend_prep_ok)
+ goto out;
+
+ ret = telemetry_raw_read_eventlog(TELEM_IOSS, evtlog,
+ TELEM_MAX_OS_ALLOCATED_EVENTS);
+ if (ret < 0)
+ goto out;
+
+ for (index = 0; index < ret; index++) {
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_occ_id,
+ suspend_shlw_ctr_exit);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_occ_id,
+ suspend_deep_ctr_exit);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_shlw_res_id,
+ suspend_shlw_res_exit);
+
+ TELEM_CHECK_AND_PARSE_CTRS(conf->s0ix_deep_res_id,
+ suspend_deep_res_exit);
+ }
+
+ if ((suspend_shlw_ctr_exit < suspend_shlw_ctr_temp) ||
+ (suspend_deep_ctr_exit < suspend_deep_ctr_temp) ||
+ (suspend_shlw_res_exit < suspend_shlw_res_temp) ||
+ (suspend_deep_res_exit < suspend_deep_res_temp)) {
+ pr_err("Wrong s0ix counters detected\n");
+ goto out;
+ }
+
+ /*
+ * Due to some design limitations in the firmware, sometimes the
+ * counters do not get updated by the time we reach here. As a
+ * workaround, we try to see if this was a genuine case of sleep
+ * failure or not by cross-checking from PMC GCR registers directly.
+ */
+ if (suspend_shlw_ctr_exit == suspend_shlw_ctr_temp &&
+ suspend_deep_ctr_exit == suspend_deep_ctr_temp) {
+ struct telemetry_plt_config *plt_config = telemetry_get_pltdata();
+ struct intel_pmc_dev *pmc = plt_config->pmc;
+
+ ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_SHLW_S0IX_REG,
+ &suspend_shlw_res_exit);
+ if (ret < 0)
+ goto out;
+
+ ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_DEEP_S0IX_REG,
+ &suspend_deep_res_exit);
+ if (ret < 0)
+ goto out;
+
+ if (suspend_shlw_res_exit > suspend_shlw_res_temp)
+ suspend_shlw_ctr_exit++;
+
+ if (suspend_deep_res_exit > suspend_deep_res_temp)
+ suspend_deep_ctr_exit++;
+ }
+
+ suspend_shlw_ctr_exit -= suspend_shlw_ctr_temp;
+ suspend_deep_ctr_exit -= suspend_deep_ctr_temp;
+ suspend_shlw_res_exit -= suspend_shlw_res_temp;
+ suspend_deep_res_exit -= suspend_deep_res_temp;
+
+ if (suspend_shlw_ctr_exit != 0) {
+ conf->suspend_stats.shlw_ctr +=
+ suspend_shlw_ctr_exit;
+
+ conf->suspend_stats.shlw_res +=
+ suspend_shlw_res_exit;
+ }
+
+ if (suspend_deep_ctr_exit != 0) {
+ conf->suspend_stats.deep_ctr +=
+ suspend_deep_ctr_exit;
+
+ conf->suspend_stats.deep_res +=
+ suspend_deep_res_exit;
+ }
+
+out:
+ suspend_prep_ok = 0;
+ return NOTIFY_OK;
+}
+
+static int pm_notification(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ return pm_suspend_prep_cb();
+ case PM_POST_SUSPEND:
+ return pm_suspend_exit_cb();
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block pm_notifier = {
+ .notifier_call = pm_notification,
+};
+
+static int __init telemetry_debugfs_init(void)
+{
+ const struct x86_cpu_id *id;
+ int err;
+ struct dentry *dir;
+
+ /* Only APL supported for now */
+ id = x86_match_cpu(telemetry_debugfs_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ debugfs_conf = (struct telemetry_debugfs_conf *)id->driver_data;
+
+ if (!telemetry_get_pltdata()) {
+ pr_info("Invalid pltconfig, ensure IPC1 device is enabled in BIOS\n");
+ return -ENODEV;
+ }
+
+ err = telemetry_debugfs_check_evts();
+ if (err < 0) {
+ pr_info("telemetry_debugfs_check_evts failed\n");
+ return -EINVAL;
+ }
+
+ register_pm_notifier(&pm_notifier);
+
+ dir = debugfs_create_dir("telemetry", NULL);
+ debugfs_conf->telemetry_dbg_dir = dir;
+
+ debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL,
+ &telem_pss_states_fops);
+ debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL,
+ &telem_ioss_states_fops);
+ debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL,
+ &telem_soc_states_fops);
+ debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL,
+ &telem_s0ix_fops);
+ debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL,
+ &telem_pss_trc_verb_ops);
+ debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir,
+ NULL, &telem_ioss_trc_verb_ops);
+ return 0;
+}
+
+static void __exit telemetry_debugfs_exit(void)
+{
+ debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir);
+ debugfs_conf->telemetry_dbg_dir = NULL;
+ unregister_pm_notifier(&pm_notifier);
+}
+
+late_initcall(telemetry_debugfs_init);
+module_exit(telemetry_debugfs_exit);
+
+MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
+MODULE_DESCRIPTION("Intel SoC Telemetry debugfs Interface");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c
new file mode 100644
index 000000000000..405dea87de6b
--- /dev/null
+++ b/drivers/platform/x86/intel/telemetry/pltdrv.c
@@ -0,0 +1,1189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel SOC Telemetry Platform Driver: Currently supports APL
+ * Copyright (c) 2015, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * This file provides the platform specific telemetry implementation for APL.
+ * It used the PUNIT and PMC IPC interfaces for configuring the counters.
+ * The accumulated results are fetched from SRAM.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+#include <asm/intel_punit_ipc.h>
+#include <asm/intel_telemetry.h>
+
+#define DRIVER_NAME "intel_telemetry"
+#define DRIVER_VERSION "1.0.0"
+
+#define TELEM_TRC_VERBOSITY_MASK 0x3
+
+#define TELEM_MIN_PERIOD(x) ((x) & 0x7F0000)
+#define TELEM_MAX_PERIOD(x) ((x) & 0x7F000000)
+#define TELEM_SAMPLE_PERIOD_INVALID(x) ((x) & (BIT(7)))
+#define TELEM_CLEAR_SAMPLE_PERIOD(x) ((x) &= ~0x7F)
+
+#define TELEM_SAMPLING_DEFAULT_PERIOD 0xD
+
+#define TELEM_MAX_EVENTS_SRAM 28
+#define TELEM_SSRAM_STARTTIME_OFFSET 8
+#define TELEM_SSRAM_EVTLOG_OFFSET 16
+
+#define IOSS_TELEM 0xeb
+#define IOSS_TELEM_EVENT_READ 0x0
+#define IOSS_TELEM_EVENT_WRITE 0x1
+#define IOSS_TELEM_INFO_READ 0x2
+#define IOSS_TELEM_TRACE_CTL_READ 0x5
+#define IOSS_TELEM_TRACE_CTL_WRITE 0x6
+#define IOSS_TELEM_EVENT_CTL_READ 0x7
+#define IOSS_TELEM_EVENT_CTL_WRITE 0x8
+#define IOSS_TELEM_EVT_WRITE_SIZE 0x3
+
+#define TELEM_INFO_SRAMEVTS_MASK 0xFF00
+#define TELEM_INFO_SRAMEVTS_SHIFT 0x8
+#define TELEM_SSRAM_READ_TIMEOUT 10
+
+#define TELEM_INFO_NENABLES_MASK 0xFF
+#define TELEM_EVENT_ENABLE 0x8000
+
+#define TELEM_MASK_BIT 1
+#define TELEM_MASK_BYTE 0xFF
+#define BYTES_PER_LONG 8
+#define TELEM_MASK_PCS_STATE 0xF
+
+#define TELEM_DISABLE(x) ((x) &= ~(BIT(31)))
+#define TELEM_CLEAR_EVENTS(x) ((x) |= (BIT(30)))
+#define TELEM_ENABLE_SRAM_EVT_TRACE(x) ((x) &= ~(BIT(30) | BIT(24)))
+#define TELEM_ENABLE_PERIODIC(x) ((x) |= (BIT(23) | BIT(31) | BIT(7)))
+#define TELEM_EXTRACT_VERBOSITY(x, y) ((y) = (((x) >> 27) & 0x3))
+#define TELEM_CLEAR_VERBOSITY_BITS(x) ((x) &= ~(BIT(27) | BIT(28)))
+#define TELEM_SET_VERBOSITY_BITS(x, y) ((x) |= ((y) << 27))
+
+enum telemetry_action {
+ TELEM_UPDATE = 0,
+ TELEM_ADD,
+ TELEM_RESET,
+ TELEM_ACTION_NONE
+};
+
+struct telem_ssram_region {
+ u64 timestamp;
+ u64 start_time;
+ u64 events[TELEM_MAX_EVENTS_SRAM];
+};
+
+static struct telemetry_plt_config *telm_conf;
+
+/*
+ * The following counters are programmed by default during setup.
+ * Only 20 allocated to kernel driver
+ */
+static struct telemetry_evtmap
+ telemetry_apl_ioss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = {
+ {"SOC_S0IX_TOTAL_RES", 0x4800},
+ {"SOC_S0IX_TOTAL_OCC", 0x4000},
+ {"SOC_S0IX_SHALLOW_RES", 0x4801},
+ {"SOC_S0IX_SHALLOW_OCC", 0x4001},
+ {"SOC_S0IX_DEEP_RES", 0x4802},
+ {"SOC_S0IX_DEEP_OCC", 0x4002},
+ {"PMC_POWER_GATE", 0x5818},
+ {"PMC_D3_STATES", 0x5819},
+ {"PMC_D0I3_STATES", 0x581A},
+ {"PMC_S0IX_WAKE_REASON_GPIO", 0x6000},
+ {"PMC_S0IX_WAKE_REASON_TIMER", 0x6001},
+ {"PMC_S0IX_WAKE_REASON_VNNREQ", 0x6002},
+ {"PMC_S0IX_WAKE_REASON_LOWPOWER", 0x6003},
+ {"PMC_S0IX_WAKE_REASON_EXTERNAL", 0x6004},
+ {"PMC_S0IX_WAKE_REASON_MISC", 0x6005},
+ {"PMC_S0IX_BLOCKING_IPS_D3_D0I3", 0x6006},
+ {"PMC_S0IX_BLOCKING_IPS_PG", 0x6007},
+ {"PMC_S0IX_BLOCKING_MISC_IPS_PG", 0x6008},
+ {"PMC_S0IX_BLOCK_IPS_VNN_REQ", 0x6009},
+ {"PMC_S0IX_BLOCK_IPS_CLOCKS", 0x600B},
+};
+
+
+static struct telemetry_evtmap
+ telemetry_apl_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = {
+ {"IA_CORE0_C6_RES", 0x0400},
+ {"IA_CORE0_C6_CTR", 0x0000},
+ {"IA_MODULE0_C7_RES", 0x0410},
+ {"IA_MODULE0_C7_CTR", 0x000E},
+ {"IA_C0_RES", 0x0805},
+ {"PCS_LTR", 0x2801},
+ {"PSTATES", 0x2802},
+ {"SOC_S0I3_RES", 0x0409},
+ {"SOC_S0I3_CTR", 0x000A},
+ {"PCS_S0I3_CTR", 0x0009},
+ {"PCS_C1E_RES", 0x041A},
+ {"PCS_IDLE_STATUS", 0x2806},
+ {"IA_PERF_LIMITS", 0x280B},
+ {"GT_PERF_LIMITS", 0x280C},
+ {"PCS_WAKEUP_S0IX_CTR", 0x0030},
+ {"PCS_IDLE_BLOCKED", 0x2C00},
+ {"PCS_S0IX_BLOCKED", 0x2C01},
+ {"PCS_S0IX_WAKE_REASONS", 0x2C02},
+ {"PCS_LTR_BLOCKING", 0x2C03},
+ {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40},
+};
+
+static struct telemetry_evtmap
+ telemetry_glk_pss_default_events[TELEM_MAX_OS_ALLOCATED_EVENTS] = {
+ {"IA_CORE0_C6_RES", 0x0400},
+ {"IA_CORE0_C6_CTR", 0x0000},
+ {"IA_MODULE0_C7_RES", 0x0410},
+ {"IA_MODULE0_C7_CTR", 0x000C},
+ {"IA_C0_RES", 0x0805},
+ {"PCS_LTR", 0x2801},
+ {"PSTATES", 0x2802},
+ {"SOC_S0I3_RES", 0x0407},
+ {"SOC_S0I3_CTR", 0x0008},
+ {"PCS_S0I3_CTR", 0x0007},
+ {"PCS_C1E_RES", 0x0414},
+ {"PCS_IDLE_STATUS", 0x2806},
+ {"IA_PERF_LIMITS", 0x280B},
+ {"GT_PERF_LIMITS", 0x280C},
+ {"PCS_WAKEUP_S0IX_CTR", 0x0025},
+ {"PCS_IDLE_BLOCKED", 0x2C00},
+ {"PCS_S0IX_BLOCKED", 0x2C01},
+ {"PCS_S0IX_WAKE_REASONS", 0x2C02},
+ {"PCS_LTR_BLOCKING", 0x2C03},
+ {"PC2_AND_MEM_SHALLOW_IDLE_RES", 0x1D40},
+};
+
+/* APL specific Data */
+static struct telemetry_plt_config telem_apl_config = {
+ .pss_config = {
+ .telem_evts = telemetry_apl_pss_default_events,
+ },
+ .ioss_config = {
+ .telem_evts = telemetry_apl_ioss_default_events,
+ },
+};
+
+/* GLK specific Data */
+static struct telemetry_plt_config telem_glk_config = {
+ .pss_config = {
+ .telem_evts = telemetry_glk_pss_default_events,
+ },
+ .ioss_config = {
+ .telem_evts = telemetry_apl_ioss_default_events,
+ },
+};
+
+static const struct x86_cpu_id telemetry_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &telem_apl_config),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, &telem_glk_config),
+ {}
+};
+
+MODULE_DEVICE_TABLE(x86cpu, telemetry_cpu_ids);
+
+static inline int telem_get_unitconfig(enum telemetry_unit telem_unit,
+ struct telemetry_unit_config **unit_config)
+{
+ if (telem_unit == TELEM_PSS)
+ *unit_config = &(telm_conf->pss_config);
+ else if (telem_unit == TELEM_IOSS)
+ *unit_config = &(telm_conf->ioss_config);
+ else
+ return -EINVAL;
+
+ return 0;
+
+}
+
+static int telemetry_check_evtid(enum telemetry_unit telem_unit,
+ u32 *evtmap, u8 len,
+ enum telemetry_action action)
+{
+ struct telemetry_unit_config *unit_config;
+ int ret;
+
+ ret = telem_get_unitconfig(telem_unit, &unit_config);
+ if (ret < 0)
+ return ret;
+
+ switch (action) {
+ case TELEM_RESET:
+ if (len > TELEM_MAX_EVENTS_SRAM)
+ return -EINVAL;
+
+ break;
+
+ case TELEM_UPDATE:
+ if (len > TELEM_MAX_EVENTS_SRAM)
+ return -EINVAL;
+
+ if ((len > 0) && (evtmap == NULL))
+ return -EINVAL;
+
+ break;
+
+ case TELEM_ADD:
+ if ((len + unit_config->ssram_evts_used) >
+ TELEM_MAX_EVENTS_SRAM)
+ return -EINVAL;
+
+ if ((len > 0) && (evtmap == NULL))
+ return -EINVAL;
+
+ break;
+
+ default:
+ pr_err("Unknown Telemetry action specified %d\n", action);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static inline int telemetry_plt_config_ioss_event(u32 evt_id, int index)
+{
+ u32 write_buf;
+
+ write_buf = evt_id | TELEM_EVENT_ENABLE;
+ write_buf <<= BITS_PER_BYTE;
+ write_buf |= index;
+
+ return intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_WRITE, &write_buf,
+ IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0);
+}
+
+static inline int telemetry_plt_config_pss_event(u32 evt_id, int index)
+{
+ u32 write_buf;
+ int ret;
+
+ write_buf = evt_id | TELEM_EVENT_ENABLE;
+ ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT,
+ index, 0, &write_buf, NULL);
+
+ return ret;
+}
+
+static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
+ enum telemetry_action action)
+{
+ struct intel_scu_ipc_dev *scu = telm_conf->scu;
+ u8 num_ioss_evts, ioss_period;
+ int ret, index, idx;
+ u32 *ioss_evtmap;
+ u32 telem_ctrl;
+
+ num_ioss_evts = evtconfig.num_evts;
+ ioss_period = evtconfig.period;
+ ioss_evtmap = evtconfig.evtmap;
+
+ /* Get telemetry EVENT CTL */
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
+ &telem_ctrl, sizeof(telem_ctrl));
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Read Failed\n");
+ return ret;
+ }
+
+ /* Disable Telemetry */
+ TELEM_DISABLE(telem_ctrl);
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE, &telem_ctrl,
+ sizeof(telem_ctrl), NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+
+
+ /* Reset Everything */
+ if (action == TELEM_RESET) {
+ /* Clear All Events */
+ TELEM_CLEAR_EVENTS(telem_ctrl);
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE,
+ &telem_ctrl, sizeof(telem_ctrl),
+ NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+ telm_conf->ioss_config.ssram_evts_used = 0;
+
+ /* Configure Events */
+ for (idx = 0; idx < num_ioss_evts; idx++) {
+ if (telemetry_plt_config_ioss_event(
+ telm_conf->ioss_config.telem_evts[idx].evt_id,
+ idx)) {
+ pr_err("IOSS TELEM_RESET Fail for data: %x\n",
+ telm_conf->ioss_config.telem_evts[idx].evt_id);
+ continue;
+ }
+ telm_conf->ioss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Re-Configure Everything */
+ if (action == TELEM_UPDATE) {
+ /* Clear All Events */
+ TELEM_CLEAR_EVENTS(telem_ctrl);
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE,
+ &telem_ctrl, sizeof(telem_ctrl),
+ NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+ telm_conf->ioss_config.ssram_evts_used = 0;
+
+ /* Configure Events */
+ for (index = 0; index < num_ioss_evts; index++) {
+ telm_conf->ioss_config.telem_evts[index].evt_id =
+ ioss_evtmap[index];
+
+ if (telemetry_plt_config_ioss_event(
+ telm_conf->ioss_config.telem_evts[index].evt_id,
+ index)) {
+ pr_err("IOSS TELEM_UPDATE Fail for Evt%x\n",
+ ioss_evtmap[index]);
+ continue;
+ }
+ telm_conf->ioss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Add some Events */
+ if (action == TELEM_ADD) {
+ /* Configure Events */
+ for (index = telm_conf->ioss_config.ssram_evts_used, idx = 0;
+ idx < num_ioss_evts; index++, idx++) {
+ telm_conf->ioss_config.telem_evts[index].evt_id =
+ ioss_evtmap[idx];
+
+ if (telemetry_plt_config_ioss_event(
+ telm_conf->ioss_config.telem_evts[index].evt_id,
+ index)) {
+ pr_err("IOSS TELEM_ADD Fail for Event %x\n",
+ ioss_evtmap[idx]);
+ continue;
+ }
+ telm_conf->ioss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Enable Periodic Telemetry Events and enable SRAM trace */
+ TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
+ TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
+ TELEM_ENABLE_PERIODIC(telem_ctrl);
+ telem_ctrl |= ioss_period;
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE,
+ &telem_ctrl, sizeof(telem_ctrl), NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
+ return ret;
+ }
+
+ telm_conf->ioss_config.curr_period = ioss_period;
+
+ return 0;
+}
+
+
+static int telemetry_setup_pssevtconfig(struct telemetry_evtconfig evtconfig,
+ enum telemetry_action action)
+{
+ u8 num_pss_evts, pss_period;
+ int ret, index, idx;
+ u32 *pss_evtmap;
+ u32 telem_ctrl;
+
+ num_pss_evts = evtconfig.num_evts;
+ pss_period = evtconfig.period;
+ pss_evtmap = evtconfig.evtmap;
+
+ /* PSS Config */
+ /* Get telemetry EVENT CTL */
+ ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
+ 0, 0, NULL, &telem_ctrl);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Read Failed\n");
+ return ret;
+ }
+
+ /* Disable Telemetry */
+ TELEM_DISABLE(telem_ctrl);
+ ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+
+ /* Reset Everything */
+ if (action == TELEM_RESET) {
+ /* Clear All Events */
+ TELEM_CLEAR_EVENTS(telem_ctrl);
+
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+ telm_conf->pss_config.ssram_evts_used = 0;
+ /* Configure Events */
+ for (idx = 0; idx < num_pss_evts; idx++) {
+ if (telemetry_plt_config_pss_event(
+ telm_conf->pss_config.telem_evts[idx].evt_id,
+ idx)) {
+ pr_err("PSS TELEM_RESET Fail for Event %x\n",
+ telm_conf->pss_config.telem_evts[idx].evt_id);
+ continue;
+ }
+ telm_conf->pss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Re-Configure Everything */
+ if (action == TELEM_UPDATE) {
+ /* Clear All Events */
+ TELEM_CLEAR_EVENTS(telem_ctrl);
+
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
+ return ret;
+ }
+ telm_conf->pss_config.ssram_evts_used = 0;
+
+ /* Configure Events */
+ for (index = 0; index < num_pss_evts; index++) {
+ telm_conf->pss_config.telem_evts[index].evt_id =
+ pss_evtmap[index];
+
+ if (telemetry_plt_config_pss_event(
+ telm_conf->pss_config.telem_evts[index].evt_id,
+ index)) {
+ pr_err("PSS TELEM_UPDATE Fail for Event %x\n",
+ pss_evtmap[index]);
+ continue;
+ }
+ telm_conf->pss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Add some Events */
+ if (action == TELEM_ADD) {
+ /* Configure Events */
+ for (index = telm_conf->pss_config.ssram_evts_used, idx = 0;
+ idx < num_pss_evts; index++, idx++) {
+
+ telm_conf->pss_config.telem_evts[index].evt_id =
+ pss_evtmap[idx];
+
+ if (telemetry_plt_config_pss_event(
+ telm_conf->pss_config.telem_evts[index].evt_id,
+ index)) {
+ pr_err("PSS TELEM_ADD Fail for Event %x\n",
+ pss_evtmap[idx]);
+ continue;
+ }
+ telm_conf->pss_config.ssram_evts_used++;
+ }
+ }
+
+ /* Enable Periodic Telemetry Events and enable SRAM trace */
+ TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
+ TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
+ TELEM_ENABLE_PERIODIC(telem_ctrl);
+ telem_ctrl |= pss_period;
+
+ ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
+ return ret;
+ }
+
+ telm_conf->pss_config.curr_period = pss_period;
+
+ return 0;
+}
+
+static int telemetry_setup_evtconfig(struct telemetry_evtconfig pss_evtconfig,
+ struct telemetry_evtconfig ioss_evtconfig,
+ enum telemetry_action action)
+{
+ int ret;
+
+ mutex_lock(&(telm_conf->telem_lock));
+
+ if ((action == TELEM_UPDATE) && (telm_conf->telem_in_use)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = telemetry_check_evtid(TELEM_PSS, pss_evtconfig.evtmap,
+ pss_evtconfig.num_evts, action);
+ if (ret)
+ goto out;
+
+ ret = telemetry_check_evtid(TELEM_IOSS, ioss_evtconfig.evtmap,
+ ioss_evtconfig.num_evts, action);
+ if (ret)
+ goto out;
+
+ if (ioss_evtconfig.num_evts) {
+ ret = telemetry_setup_iossevtconfig(ioss_evtconfig, action);
+ if (ret)
+ goto out;
+ }
+
+ if (pss_evtconfig.num_evts) {
+ ret = telemetry_setup_pssevtconfig(pss_evtconfig, action);
+ if (ret)
+ goto out;
+ }
+
+ if ((action == TELEM_UPDATE) || (action == TELEM_ADD))
+ telm_conf->telem_in_use = true;
+ else
+ telm_conf->telem_in_use = false;
+
+out:
+ mutex_unlock(&(telm_conf->telem_lock));
+ return ret;
+}
+
+static int telemetry_setup(struct platform_device *pdev)
+{
+ struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
+ u32 read_buf, events, event_regs;
+ int ret;
+
+ ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
+ IOSS_TELEM_INFO_READ, NULL, 0,
+ &read_buf, sizeof(read_buf));
+ if (ret) {
+ dev_err(&pdev->dev, "IOSS TELEM_INFO Read Failed\n");
+ return ret;
+ }
+
+ /* Get telemetry Info */
+ events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >>
+ TELEM_INFO_SRAMEVTS_SHIFT;
+ event_regs = read_buf & TELEM_INFO_NENABLES_MASK;
+ if ((events < TELEM_MAX_EVENTS_SRAM) ||
+ (event_regs < TELEM_MAX_EVENTS_SRAM)) {
+ dev_err(&pdev->dev, "IOSS:Insufficient Space for SRAM Trace\n");
+ dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n",
+ events, event_regs);
+ return -ENOMEM;
+ }
+
+ telm_conf->ioss_config.min_period = TELEM_MIN_PERIOD(read_buf);
+ telm_conf->ioss_config.max_period = TELEM_MAX_PERIOD(read_buf);
+
+ /* PUNIT Mailbox Setup */
+ ret = intel_punit_ipc_command(IPC_PUNIT_BIOS_READ_TELE_INFO, 0, 0,
+ NULL, &read_buf);
+ if (ret) {
+ dev_err(&pdev->dev, "PSS TELEM_INFO Read Failed\n");
+ return ret;
+ }
+
+ /* Get telemetry Info */
+ events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >>
+ TELEM_INFO_SRAMEVTS_SHIFT;
+ event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK;
+ if ((events < TELEM_MAX_EVENTS_SRAM) ||
+ (event_regs < TELEM_MAX_EVENTS_SRAM)) {
+ dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n");
+ dev_err(&pdev->dev, "SRAM Events %d; Event Regs %d\n",
+ events, event_regs);
+ return -ENOMEM;
+ }
+
+ telm_conf->pss_config.min_period = TELEM_MIN_PERIOD(read_buf);
+ telm_conf->pss_config.max_period = TELEM_MAX_PERIOD(read_buf);
+
+ pss_evtconfig.evtmap = NULL;
+ pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
+ pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
+
+ ioss_evtconfig.evtmap = NULL;
+ ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
+ ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
+
+ ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
+ TELEM_RESET);
+ if (ret) {
+ dev_err(&pdev->dev, "TELEMETRY Setup Failed\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig,
+ struct telemetry_evtconfig ioss_evtconfig)
+{
+ int ret;
+
+ if ((pss_evtconfig.num_evts > 0) &&
+ (TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) {
+ pr_err("PSS Sampling Period Out of Range\n");
+ return -EINVAL;
+ }
+
+ if ((ioss_evtconfig.num_evts > 0) &&
+ (TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) {
+ pr_err("IOSS Sampling Period Out of Range\n");
+ return -EINVAL;
+ }
+
+ ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
+ TELEM_UPDATE);
+ if (ret)
+ pr_err("TELEMETRY Config Failed\n");
+
+ return ret;
+}
+
+
+static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
+{
+ u32 telem_ctrl = 0;
+ int ret = 0;
+
+ mutex_lock(&(telm_conf->telem_lock));
+ if (ioss_period) {
+ struct intel_scu_ipc_dev *scu = telm_conf->scu;
+
+ if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) {
+ pr_err("IOSS Sampling Period Out of Range\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Get telemetry EVENT CTL */
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
+ &telem_ctrl, sizeof(telem_ctrl));
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Read Failed\n");
+ goto out;
+ }
+
+ /* Disable Telemetry */
+ TELEM_DISABLE(telem_ctrl);
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE,
+ &telem_ctrl, sizeof(telem_ctrl),
+ NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
+ goto out;
+ }
+
+ /* Enable Periodic Telemetry Events and enable SRAM trace */
+ TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
+ TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
+ TELEM_ENABLE_PERIODIC(telem_ctrl);
+ telem_ctrl |= ioss_period;
+
+ ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
+ IOSS_TELEM_EVENT_CTL_WRITE,
+ &telem_ctrl, sizeof(telem_ctrl),
+ NULL, 0);
+ if (ret) {
+ pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
+ goto out;
+ }
+ telm_conf->ioss_config.curr_period = ioss_period;
+ }
+
+ if (pss_period) {
+ if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) {
+ pr_err("PSS Sampling Period Out of Range\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Get telemetry EVENT CTL */
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
+ 0, 0, NULL, &telem_ctrl);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Read Failed\n");
+ goto out;
+ }
+
+ /* Disable Telemetry */
+ TELEM_DISABLE(telem_ctrl);
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
+ goto out;
+ }
+
+ /* Enable Periodic Telemetry Events and enable SRAM trace */
+ TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
+ TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
+ TELEM_ENABLE_PERIODIC(telem_ctrl);
+ telem_ctrl |= pss_period;
+
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
+ 0, 0, &telem_ctrl, NULL);
+ if (ret) {
+ pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
+ goto out;
+ }
+ telm_conf->pss_config.curr_period = pss_period;
+ }
+
+out:
+ mutex_unlock(&(telm_conf->telem_lock));
+ return ret;
+}
+
+
+static int telemetry_plt_get_sampling_period(u8 *pss_min_period,
+ u8 *pss_max_period,
+ u8 *ioss_min_period,
+ u8 *ioss_max_period)
+{
+ *pss_min_period = telm_conf->pss_config.min_period;
+ *pss_max_period = telm_conf->pss_config.max_period;
+ *ioss_min_period = telm_conf->ioss_config.min_period;
+ *ioss_max_period = telm_conf->ioss_config.max_period;
+
+ return 0;
+}
+
+
+static int telemetry_plt_reset_events(void)
+{
+ struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
+ int ret;
+
+ pss_evtconfig.evtmap = NULL;
+ pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
+ pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
+
+ ioss_evtconfig.evtmap = NULL;
+ ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
+ ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
+
+ ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
+ TELEM_RESET);
+ if (ret)
+ pr_err("TELEMETRY Reset Failed\n");
+
+ return ret;
+}
+
+
+static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config,
+ struct telemetry_evtconfig *ioss_config,
+ int pss_len, int ioss_len)
+{
+ u32 *pss_evtmap, *ioss_evtmap;
+ u32 index;
+
+ pss_evtmap = pss_config->evtmap;
+ ioss_evtmap = ioss_config->evtmap;
+
+ mutex_lock(&(telm_conf->telem_lock));
+ pss_config->num_evts = telm_conf->pss_config.ssram_evts_used;
+ ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used;
+
+ pss_config->period = telm_conf->pss_config.curr_period;
+ ioss_config->period = telm_conf->ioss_config.curr_period;
+
+ if ((pss_len < telm_conf->pss_config.ssram_evts_used) ||
+ (ioss_len < telm_conf->ioss_config.ssram_evts_used)) {
+ mutex_unlock(&(telm_conf->telem_lock));
+ return -EINVAL;
+ }
+
+ for (index = 0; index < telm_conf->pss_config.ssram_evts_used;
+ index++) {
+ pss_evtmap[index] =
+ telm_conf->pss_config.telem_evts[index].evt_id;
+ }
+
+ for (index = 0; index < telm_conf->ioss_config.ssram_evts_used;
+ index++) {
+ ioss_evtmap[index] =
+ telm_conf->ioss_config.telem_evts[index].evt_id;
+ }
+
+ mutex_unlock(&(telm_conf->telem_lock));
+ return 0;
+}
+
+
+static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts,
+ u32 *pss_evtmap, u32 *ioss_evtmap)
+{
+ struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
+ int ret;
+
+ pss_evtconfig.evtmap = pss_evtmap;
+ pss_evtconfig.num_evts = num_pss_evts;
+ pss_evtconfig.period = telm_conf->pss_config.curr_period;
+
+ ioss_evtconfig.evtmap = ioss_evtmap;
+ ioss_evtconfig.num_evts = num_ioss_evts;
+ ioss_evtconfig.period = telm_conf->ioss_config.curr_period;
+
+ ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
+ TELEM_ADD);
+ if (ret)
+ pr_err("TELEMETRY ADD Failed\n");
+
+ return ret;
+}
+
+static int telem_evtlog_read(enum telemetry_unit telem_unit,
+ struct telem_ssram_region *ssram_region, u8 len)
+{
+ struct telemetry_unit_config *unit_config;
+ u64 timestamp_prev, timestamp_next;
+ int ret, index, timeout = 0;
+
+ ret = telem_get_unitconfig(telem_unit, &unit_config);
+ if (ret < 0)
+ return ret;
+
+ if (len > unit_config->ssram_evts_used)
+ len = unit_config->ssram_evts_used;
+
+ do {
+ timestamp_prev = readq(unit_config->regmap);
+ if (!timestamp_prev) {
+ pr_err("Ssram under update. Please Try Later\n");
+ return -EBUSY;
+ }
+
+ ssram_region->start_time = readq(unit_config->regmap +
+ TELEM_SSRAM_STARTTIME_OFFSET);
+
+ for (index = 0; index < len; index++) {
+ ssram_region->events[index] =
+ readq(unit_config->regmap + TELEM_SSRAM_EVTLOG_OFFSET +
+ BYTES_PER_LONG*index);
+ }
+
+ timestamp_next = readq(unit_config->regmap);
+ if (!timestamp_next) {
+ pr_err("Ssram under update. Please Try Later\n");
+ return -EBUSY;
+ }
+
+ if (timeout++ > TELEM_SSRAM_READ_TIMEOUT) {
+ pr_err("Timeout while reading Events\n");
+ return -EBUSY;
+ }
+
+ } while (timestamp_prev != timestamp_next);
+
+ ssram_region->timestamp = timestamp_next;
+
+ return len;
+}
+
+static int telemetry_plt_raw_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog,
+ int len, int log_all_evts)
+{
+ int index, idx1, ret, readlen = len;
+ struct telem_ssram_region ssram_region;
+ struct telemetry_evtmap *evtmap;
+
+ switch (telem_unit) {
+ case TELEM_PSS:
+ evtmap = telm_conf->pss_config.telem_evts;
+ break;
+
+ case TELEM_IOSS:
+ evtmap = telm_conf->ioss_config.telem_evts;
+ break;
+
+ default:
+ pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
+ return -EINVAL;
+ }
+
+ if (!log_all_evts)
+ readlen = TELEM_MAX_EVENTS_SRAM;
+
+ ret = telem_evtlog_read(telem_unit, &ssram_region, readlen);
+ if (ret < 0)
+ return ret;
+
+ /* Invalid evt-id array specified via length mismatch */
+ if ((!log_all_evts) && (len > ret))
+ return -EINVAL;
+
+ if (log_all_evts)
+ for (index = 0; index < ret; index++) {
+ evtlog[index].telem_evtlog = ssram_region.events[index];
+ evtlog[index].telem_evtid = evtmap[index].evt_id;
+ }
+ else
+ for (index = 0, readlen = 0; (index < ret) && (readlen < len);
+ index++) {
+ for (idx1 = 0; idx1 < len; idx1++) {
+ /* Elements matched */
+ if (evtmap[index].evt_id ==
+ evtlog[idx1].telem_evtid) {
+ evtlog[idx1].telem_evtlog =
+ ssram_region.events[index];
+ readlen++;
+
+ break;
+ }
+ }
+ }
+
+ return readlen;
+}
+
+static int telemetry_plt_read_eventlog(enum telemetry_unit telem_unit,
+ struct telemetry_evtlog *evtlog, int len, int log_all_evts)
+{
+ int ret;
+
+ mutex_lock(&(telm_conf->telem_lock));
+ ret = telemetry_plt_raw_read_eventlog(telem_unit, evtlog,
+ len, log_all_evts);
+ mutex_unlock(&(telm_conf->telem_lock));
+
+ return ret;
+}
+
+static int telemetry_plt_get_trace_verbosity(enum telemetry_unit telem_unit,
+ u32 *verbosity)
+{
+ u32 temp = 0;
+ int ret;
+
+ if (verbosity == NULL)
+ return -EINVAL;
+
+ mutex_lock(&(telm_conf->telem_trace_lock));
+ switch (telem_unit) {
+ case TELEM_PSS:
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL,
+ 0, 0, NULL, &temp);
+ if (ret) {
+ pr_err("PSS TRACE_CTRL Read Failed\n");
+ goto out;
+ }
+
+ break;
+
+ case TELEM_IOSS:
+ ret = intel_scu_ipc_dev_command(telm_conf->scu,
+ IOSS_TELEM, IOSS_TELEM_TRACE_CTL_READ,
+ NULL, 0, &temp, sizeof(temp));
+ if (ret) {
+ pr_err("IOSS TRACE_CTL Read Failed\n");
+ goto out;
+ }
+
+ break;
+
+ default:
+ pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
+ ret = -EINVAL;
+ break;
+ }
+ TELEM_EXTRACT_VERBOSITY(temp, *verbosity);
+
+out:
+ mutex_unlock(&(telm_conf->telem_trace_lock));
+ return ret;
+}
+
+static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit,
+ u32 verbosity)
+{
+ u32 temp = 0;
+ int ret;
+
+ verbosity &= TELEM_TRC_VERBOSITY_MASK;
+
+ mutex_lock(&(telm_conf->telem_trace_lock));
+ switch (telem_unit) {
+ case TELEM_PSS:
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_READ_TELE_TRACE_CTRL,
+ 0, 0, NULL, &temp);
+ if (ret) {
+ pr_err("PSS TRACE_CTRL Read Failed\n");
+ goto out;
+ }
+
+ TELEM_CLEAR_VERBOSITY_BITS(temp);
+ TELEM_SET_VERBOSITY_BITS(temp, verbosity);
+
+ ret = intel_punit_ipc_command(
+ IPC_PUNIT_BIOS_WRITE_TELE_TRACE_CTRL,
+ 0, 0, &temp, NULL);
+ if (ret) {
+ pr_err("PSS TRACE_CTRL Verbosity Set Failed\n");
+ goto out;
+ }
+ break;
+
+ case TELEM_IOSS:
+ ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
+ IOSS_TELEM_TRACE_CTL_READ,
+ NULL, 0, &temp, sizeof(temp));
+ if (ret) {
+ pr_err("IOSS TRACE_CTL Read Failed\n");
+ goto out;
+ }
+
+ TELEM_CLEAR_VERBOSITY_BITS(temp);
+ TELEM_SET_VERBOSITY_BITS(temp, verbosity);
+
+ ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
+ IOSS_TELEM_TRACE_CTL_WRITE,
+ &temp, sizeof(temp), NULL, 0);
+ if (ret) {
+ pr_err("IOSS TRACE_CTL Verbosity Set Failed\n");
+ goto out;
+ }
+ break;
+
+ default:
+ pr_err("Unknown Telemetry Unit Specified %d\n", telem_unit);
+ ret = -EINVAL;
+ break;
+ }
+
+out:
+ mutex_unlock(&(telm_conf->telem_trace_lock));
+ return ret;
+}
+
+static const struct telemetry_core_ops telm_pltops = {
+ .get_trace_verbosity = telemetry_plt_get_trace_verbosity,
+ .set_trace_verbosity = telemetry_plt_set_trace_verbosity,
+ .set_sampling_period = telemetry_plt_set_sampling_period,
+ .get_sampling_period = telemetry_plt_get_sampling_period,
+ .raw_read_eventlog = telemetry_plt_raw_read_eventlog,
+ .get_eventconfig = telemetry_plt_get_eventconfig,
+ .update_events = telemetry_plt_update_events,
+ .read_eventlog = telemetry_plt_read_eventlog,
+ .reset_events = telemetry_plt_reset_events,
+ .add_events = telemetry_plt_add_events,
+};
+
+static int telemetry_pltdrv_probe(struct platform_device *pdev)
+{
+ const struct x86_cpu_id *id;
+ void __iomem *mem;
+ int ret;
+
+ id = x86_match_cpu(telemetry_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ telm_conf = (struct telemetry_plt_config *)id->driver_data;
+
+ telm_conf->pmc = dev_get_drvdata(pdev->dev.parent);
+
+ mem = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ telm_conf->pss_config.regmap = mem;
+
+ mem = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ telm_conf->ioss_config.regmap = mem;
+
+ telm_conf->scu = devm_intel_scu_ipc_dev_get(&pdev->dev);
+ if (!telm_conf->scu) {
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ mutex_init(&telm_conf->telem_lock);
+ mutex_init(&telm_conf->telem_trace_lock);
+
+ ret = telemetry_setup(pdev);
+ if (ret)
+ goto out;
+
+ ret = telemetry_set_pltdata(&telm_pltops, telm_conf);
+ if (ret) {
+ dev_err(&pdev->dev, "TELEMETRY Set Pltops Failed.\n");
+ goto out;
+ }
+
+ return 0;
+
+out:
+ dev_err(&pdev->dev, "TELEMETRY Setup Failed.\n");
+
+ return ret;
+}
+
+static int telemetry_pltdrv_remove(struct platform_device *pdev)
+{
+ telemetry_clear_pltdata();
+ return 0;
+}
+
+static struct platform_driver telemetry_soc_driver = {
+ .probe = telemetry_pltdrv_probe,
+ .remove = telemetry_pltdrv_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init telemetry_module_init(void)
+{
+ return platform_driver_register(&telemetry_soc_driver);
+}
+
+static void __exit telemetry_module_exit(void)
+{
+ platform_driver_unregister(&telemetry_soc_driver);
+}
+
+device_initcall(telemetry_module_init);
+module_exit(telemetry_module_exit);
+
+MODULE_AUTHOR("Souvik Kumar Chakravarty <souvik.k.chakravarty@intel.com>");
+MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/turbo_max_3.c b/drivers/platform/x86/intel/turbo_max_3.c
new file mode 100644
index 000000000000..892140b62898
--- /dev/null
+++ b/drivers/platform/x86/intel/turbo_max_3.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Turbo Boost Max Technology 3.0 legacy (non HWP) enumeration driver
+ * Copyright (c) 2017, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpufeature.h>
+#include <linux/cpuhotplug.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/topology.h>
+#include <linux/workqueue.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#define MSR_OC_MAILBOX 0x150
+#define MSR_OC_MAILBOX_CMD_OFFSET 32
+#define MSR_OC_MAILBOX_RSP_OFFSET 32
+#define MSR_OC_MAILBOX_BUSY_BIT 63
+#define OC_MAILBOX_FC_CONTROL_CMD 0x1C
+
+/*
+ * Typical latency to get mail box response is ~3us, It takes +3 us to
+ * process reading mailbox after issuing mailbox write on a Broadwell 3.4 GHz
+ * system. So for most of the time, the first mailbox read should have the
+ * response, but to avoid some boundary cases retry twice.
+ */
+#define OC_MAILBOX_RETRY_COUNT 2
+
+static int get_oc_core_priority(unsigned int cpu)
+{
+ u64 value, cmd = OC_MAILBOX_FC_CONTROL_CMD;
+ int ret, i;
+
+ /* Issue favored core read command */
+ value = cmd << MSR_OC_MAILBOX_CMD_OFFSET;
+ /* Set the busy bit to indicate OS is trying to issue command */
+ value |= BIT_ULL(MSR_OC_MAILBOX_BUSY_BIT);
+ ret = wrmsrl_safe(MSR_OC_MAILBOX, value);
+ if (ret) {
+ pr_debug("cpu %d OC mailbox write failed\n", cpu);
+ return ret;
+ }
+
+ for (i = 0; i < OC_MAILBOX_RETRY_COUNT; ++i) {
+ ret = rdmsrl_safe(MSR_OC_MAILBOX, &value);
+ if (ret) {
+ pr_debug("cpu %d OC mailbox read failed\n", cpu);
+ break;
+ }
+
+ if (value & BIT_ULL(MSR_OC_MAILBOX_BUSY_BIT)) {
+ pr_debug("cpu %d OC mailbox still processing\n", cpu);
+ ret = -EBUSY;
+ continue;
+ }
+
+ if ((value >> MSR_OC_MAILBOX_RSP_OFFSET) & 0xff) {
+ pr_debug("cpu %d OC mailbox cmd failed\n", cpu);
+ ret = -ENXIO;
+ break;
+ }
+
+ ret = value & 0xff;
+ pr_debug("cpu %d max_ratio %d\n", cpu, ret);
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * The work item is needed to avoid CPU hotplug locking issues. The function
+ * itmt_legacy_set_priority() is called from CPU online callback, so can't
+ * call sched_set_itmt_support() from there as this function will aquire
+ * hotplug locks in its path.
+ */
+static void itmt_legacy_work_fn(struct work_struct *work)
+{
+ sched_set_itmt_support();
+}
+
+static DECLARE_WORK(sched_itmt_work, itmt_legacy_work_fn);
+
+static int itmt_legacy_cpu_online(unsigned int cpu)
+{
+ static u32 max_highest_perf = 0, min_highest_perf = U32_MAX;
+ int priority;
+
+ priority = get_oc_core_priority(cpu);
+ if (priority < 0)
+ return 0;
+
+ sched_set_itmt_core_prio(priority, cpu);
+
+ /* Enable ITMT feature when a core with different priority is found */
+ if (max_highest_perf <= min_highest_perf) {
+ if (priority > max_highest_perf)
+ max_highest_perf = priority;
+
+ if (priority < min_highest_perf)
+ min_highest_perf = priority;
+
+ if (max_highest_perf > min_highest_perf)
+ schedule_work(&sched_itmt_work);
+ }
+
+ return 0;
+}
+
+static const struct x86_cpu_id itmt_legacy_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
+ {}
+};
+
+static int __init itmt_legacy_init(void)
+{
+ const struct x86_cpu_id *id;
+ int ret;
+
+ id = x86_match_cpu(itmt_legacy_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "platform/x86/turbo_max_3:online",
+ itmt_legacy_cpu_online, NULL);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+late_initcall(itmt_legacy_init)
diff --git a/drivers/platform/x86/intel/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency.c
new file mode 100644
index 000000000000..3ee4c5c8a64f
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Uncore Frequency Setting
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Provide interface to set MSR 620 at a granularity of per die. On CPU online,
+ * one control CPU is identified per die to read/write limit. This control CPU
+ * is changed, if the CPU state is changed to offline. When the last CPU is
+ * offline in a die then remove the sysfs object for that die.
+ * The majority of actual code is related to sysfs create and read/write
+ * attributes.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpu.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#define MSR_UNCORE_RATIO_LIMIT 0x620
+#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
+
+/**
+ * struct uncore_data - Encapsulate all uncore data
+ * @stored_uncore_data: Last user changed MSR 620 value, which will be restored
+ * on system resume.
+ * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init
+ * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init
+ * @control_cpu: Designated CPU for a die to read/write
+ * @valid: Mark the data valid/invalid
+ *
+ * This structure is used to encapsulate all data related to uncore sysfs
+ * settings for a die/package.
+ */
+struct uncore_data {
+ struct kobject kobj;
+ struct completion kobj_unregister;
+ u64 stored_uncore_data;
+ u32 initial_min_freq_khz;
+ u32 initial_max_freq_khz;
+ int control_cpu;
+ bool valid;
+};
+
+#define to_uncore_data(a) container_of(a, struct uncore_data, kobj)
+
+/* Max instances for uncore data, one for each die */
+static int uncore_max_entries __read_mostly;
+/* Storage for uncore data for all instances */
+static struct uncore_data *uncore_instances;
+/* Root of the all uncore sysfs kobjs */
+static struct kobject *uncore_root_kobj;
+/* Stores the CPU mask of the target CPUs to use during uncore read/write */
+static cpumask_t uncore_cpu_mask;
+/* CPU online callback register instance */
+static enum cpuhp_state uncore_hp_state __read_mostly;
+/* Mutex to control all mutual exclusions */
+static DEFINE_MUTEX(uncore_lock);
+
+struct uncore_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct kobject *kobj,
+ struct attribute *attr, char *buf);
+ ssize_t (*store)(struct kobject *kobj,
+ struct attribute *attr, const char *c, ssize_t count);
+};
+
+#define define_one_uncore_ro(_name) \
+static struct uncore_attr _name = \
+__ATTR(_name, 0444, show_##_name, NULL)
+
+#define define_one_uncore_rw(_name) \
+static struct uncore_attr _name = \
+__ATTR(_name, 0644, show_##_name, store_##_name)
+
+#define show_uncore_data(member_name) \
+ static ssize_t show_##member_name(struct kobject *kobj, \
+ struct attribute *attr, \
+ char *buf) \
+ { \
+ struct uncore_data *data = to_uncore_data(kobj); \
+ return scnprintf(buf, PAGE_SIZE, "%u\n", \
+ data->member_name); \
+ } \
+ define_one_uncore_ro(member_name)
+
+show_uncore_data(initial_min_freq_khz);
+show_uncore_data(initial_max_freq_khz);
+
+/* Common function to read MSR 0x620 and read min/max */
+static int uncore_read_ratio(struct uncore_data *data, unsigned int *min,
+ unsigned int *max)
+{
+ u64 cap;
+ int ret;
+
+ if (data->control_cpu < 0)
+ return -ENXIO;
+
+ ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ if (ret)
+ return ret;
+
+ *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
+ *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER;
+
+ return 0;
+}
+
+/* Common function to set min/max ratios to be used by sysfs callbacks */
+static int uncore_write_ratio(struct uncore_data *data, unsigned int input,
+ int set_max)
+{
+ int ret;
+ u64 cap;
+
+ mutex_lock(&uncore_lock);
+
+ if (data->control_cpu < 0) {
+ ret = -ENXIO;
+ goto finish_write;
+ }
+
+ input /= UNCORE_FREQ_KHZ_MULTIPLIER;
+ if (!input || input > 0x7F) {
+ ret = -EINVAL;
+ goto finish_write;
+ }
+
+ ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ if (ret)
+ goto finish_write;
+
+ if (set_max) {
+ cap &= ~0x7F;
+ cap |= input;
+ } else {
+ cap &= ~GENMASK(14, 8);
+ cap |= (input << 8);
+ }
+
+ ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
+ if (ret)
+ goto finish_write;
+
+ data->stored_uncore_data = cap;
+
+finish_write:
+ mutex_unlock(&uncore_lock);
+
+ return ret;
+}
+
+static ssize_t store_min_max_freq_khz(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, ssize_t count,
+ int min_max)
+{
+ struct uncore_data *data = to_uncore_data(kobj);
+ unsigned int input;
+
+ if (kstrtouint(buf, 10, &input))
+ return -EINVAL;
+
+ uncore_write_ratio(data, input, min_max);
+
+ return count;
+}
+
+static ssize_t show_min_max_freq_khz(struct kobject *kobj,
+ struct attribute *attr,
+ char *buf, int min_max)
+{
+ struct uncore_data *data = to_uncore_data(kobj);
+ unsigned int min, max;
+ int ret;
+
+ mutex_lock(&uncore_lock);
+ ret = uncore_read_ratio(data, &min, &max);
+ mutex_unlock(&uncore_lock);
+ if (ret)
+ return ret;
+
+ if (min_max)
+ return sprintf(buf, "%u\n", max);
+
+ return sprintf(buf, "%u\n", min);
+}
+
+#define store_uncore_min_max(name, min_max) \
+ static ssize_t store_##name(struct kobject *kobj, \
+ struct attribute *attr, \
+ const char *buf, ssize_t count) \
+ { \
+ \
+ return store_min_max_freq_khz(kobj, attr, buf, count, \
+ min_max); \
+ }
+
+#define show_uncore_min_max(name, min_max) \
+ static ssize_t show_##name(struct kobject *kobj, \
+ struct attribute *attr, char *buf) \
+ { \
+ \
+ return show_min_max_freq_khz(kobj, attr, buf, min_max); \
+ }
+
+store_uncore_min_max(min_freq_khz, 0);
+store_uncore_min_max(max_freq_khz, 1);
+
+show_uncore_min_max(min_freq_khz, 0);
+show_uncore_min_max(max_freq_khz, 1);
+
+define_one_uncore_rw(min_freq_khz);
+define_one_uncore_rw(max_freq_khz);
+
+static struct attribute *uncore_attrs[] = {
+ &initial_min_freq_khz.attr,
+ &initial_max_freq_khz.attr,
+ &max_freq_khz.attr,
+ &min_freq_khz.attr,
+ NULL
+};
+
+static void uncore_sysfs_entry_release(struct kobject *kobj)
+{
+ struct uncore_data *data = to_uncore_data(kobj);
+
+ complete(&data->kobj_unregister);
+}
+
+static struct kobj_type uncore_ktype = {
+ .release = uncore_sysfs_entry_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_attrs = uncore_attrs,
+};
+
+/* Caller provides protection */
+static struct uncore_data *uncore_get_instance(unsigned int cpu)
+{
+ int id = topology_logical_die_id(cpu);
+
+ if (id >= 0 && id < uncore_max_entries)
+ return &uncore_instances[id];
+
+ return NULL;
+}
+
+static void uncore_add_die_entry(int cpu)
+{
+ struct uncore_data *data;
+
+ mutex_lock(&uncore_lock);
+ data = uncore_get_instance(cpu);
+ if (!data) {
+ mutex_unlock(&uncore_lock);
+ return;
+ }
+
+ if (data->valid) {
+ /* control cpu changed */
+ data->control_cpu = cpu;
+ } else {
+ char str[64];
+ int ret;
+
+ memset(data, 0, sizeof(*data));
+ sprintf(str, "package_%02d_die_%02d",
+ topology_physical_package_id(cpu),
+ topology_die_id(cpu));
+
+ uncore_read_ratio(data, &data->initial_min_freq_khz,
+ &data->initial_max_freq_khz);
+
+ init_completion(&data->kobj_unregister);
+
+ ret = kobject_init_and_add(&data->kobj, &uncore_ktype,
+ uncore_root_kobj, str);
+ if (!ret) {
+ data->control_cpu = cpu;
+ data->valid = true;
+ }
+ }
+ mutex_unlock(&uncore_lock);
+}
+
+/* Last CPU in this die is offline, make control cpu invalid */
+static void uncore_remove_die_entry(int cpu)
+{
+ struct uncore_data *data;
+
+ mutex_lock(&uncore_lock);
+ data = uncore_get_instance(cpu);
+ if (data)
+ data->control_cpu = -1;
+ mutex_unlock(&uncore_lock);
+}
+
+static int uncore_event_cpu_online(unsigned int cpu)
+{
+ int target;
+
+ /* Check if there is an online cpu in the package for uncore MSR */
+ target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu));
+ if (target < nr_cpu_ids)
+ return 0;
+
+ /* Use this CPU on this die as a control CPU */
+ cpumask_set_cpu(cpu, &uncore_cpu_mask);
+ uncore_add_die_entry(cpu);
+
+ return 0;
+}
+
+static int uncore_event_cpu_offline(unsigned int cpu)
+{
+ int target;
+
+ /* Check if existing cpu is used for uncore MSRs */
+ if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask))
+ return 0;
+
+ /* Find a new cpu to set uncore MSR */
+ target = cpumask_any_but(topology_die_cpumask(cpu), cpu);
+
+ if (target < nr_cpu_ids) {
+ cpumask_set_cpu(target, &uncore_cpu_mask);
+ uncore_add_die_entry(target);
+ } else {
+ uncore_remove_die_entry(cpu);
+ }
+
+ return 0;
+}
+
+static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
+ void *_unused)
+{
+ int cpu;
+
+ switch (mode) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ for_each_cpu(cpu, &uncore_cpu_mask) {
+ struct uncore_data *data;
+ int ret;
+
+ data = uncore_get_instance(cpu);
+ if (!data || !data->valid || !data->stored_uncore_data)
+ continue;
+
+ ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT,
+ data->stored_uncore_data);
+ if (ret)
+ return ret;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block uncore_pm_nb = {
+ .notifier_call = uncore_pm_notify,
+};
+
+static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
+ {}
+};
+
+static int __init intel_uncore_init(void)
+{
+ const struct x86_cpu_id *id;
+ int ret;
+
+ id = x86_match_cpu(intel_uncore_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ uncore_max_entries = topology_max_packages() *
+ topology_max_die_per_package();
+ uncore_instances = kcalloc(uncore_max_entries,
+ sizeof(*uncore_instances), GFP_KERNEL);
+ if (!uncore_instances)
+ return -ENOMEM;
+
+ uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency",
+ &cpu_subsys.dev_root->kobj);
+ if (!uncore_root_kobj) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "platform/x86/uncore-freq:online",
+ uncore_event_cpu_online,
+ uncore_event_cpu_offline);
+ if (ret < 0)
+ goto err_rem_kobj;
+
+ uncore_hp_state = ret;
+
+ ret = register_pm_notifier(&uncore_pm_nb);
+ if (ret)
+ goto err_rem_state;
+
+ return 0;
+
+err_rem_state:
+ cpuhp_remove_state(uncore_hp_state);
+err_rem_kobj:
+ kobject_put(uncore_root_kobj);
+err_free:
+ kfree(uncore_instances);
+
+ return ret;
+}
+module_init(intel_uncore_init)
+
+static void __exit intel_uncore_exit(void)
+{
+ int i;
+
+ unregister_pm_notifier(&uncore_pm_nb);
+ cpuhp_remove_state(uncore_hp_state);
+ for (i = 0; i < uncore_max_entries; ++i) {
+ if (uncore_instances[i].valid) {
+ kobject_put(&uncore_instances[i].kobj);
+ wait_for_completion(&uncore_instances[i].kobj_unregister);
+ }
+ }
+ kobject_put(uncore_root_kobj);
+ kfree(uncore_instances);
+}
+module_exit(intel_uncore_exit)
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver");
diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c
new file mode 100644
index 000000000000..15f013af9e62
--- /dev/null
+++ b/drivers/platform/x86/intel/vbtn.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel Virtual Button driver for Windows 8.1+
+ *
+ * Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com>
+ * Copyright (C) 2016 Alex Hung <alex.hung@canonical.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include "../dual_accel_detect.h"
+
+/* Returned when NOT in tablet mode on some HP Stream x360 11 models */
+#define VGBS_TABLET_MODE_FLAG_ALT 0x10
+/* When NOT in tablet mode, VGBS returns with the flag 0x40 */
+#define VGBS_TABLET_MODE_FLAG 0x40
+#define VGBS_DOCK_MODE_FLAG 0x80
+
+#define VGBS_TABLET_MODE_FLAGS (VGBS_TABLET_MODE_FLAG | VGBS_TABLET_MODE_FLAG_ALT)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("AceLan Kao");
+
+static const struct acpi_device_id intel_vbtn_ids[] = {
+ {"INT33D6", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, intel_vbtn_ids);
+
+/* In theory, these are HID usages. */
+static const struct key_entry intel_vbtn_keymap[] = {
+ { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */
+ { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */
+ { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */
+ { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */
+ { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */
+ { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */
+ { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */
+ { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */
+ { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */
+ { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */
+ { KE_END }
+};
+
+static const struct key_entry intel_vbtn_switchmap[] = {
+ /*
+ * SW_DOCK should only be reported for docking stations, but DSDTs using the
+ * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set
+ * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0).
+ * This causes userspace to think the laptop is docked to a port-replicator
+ * and to disable suspend-on-lid-close, which is undesirable.
+ * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting.
+ */
+ { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */
+ { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */
+ { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */
+ { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */
+ { KE_END }
+};
+
+struct intel_vbtn_priv {
+ struct input_dev *buttons_dev;
+ struct input_dev *switches_dev;
+ bool dual_accel;
+ bool has_buttons;
+ bool has_switches;
+ bool wakeup_mode;
+};
+
+static void detect_tablet_mode(struct platform_device *device)
+{
+ struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ unsigned long long vgbs;
+ acpi_status status;
+ int m;
+
+ status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
+ if (ACPI_FAILURE(status))
+ return;
+
+ m = !(vgbs & VGBS_TABLET_MODE_FLAGS);
+ input_report_switch(priv->switches_dev, SW_TABLET_MODE, m);
+ m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0;
+ input_report_switch(priv->switches_dev, SW_DOCK, m);
+}
+
+/*
+ * Note this unconditionally creates the 2 input_dev-s and sets up
+ * the sparse-keymaps. Only the registration is conditional on
+ * have_buttons / have_switches. This is done so that the notify
+ * handler can always call sparse_keymap_entry_from_scancode()
+ * on the input_dev-s do determine the event type.
+ */
+static int intel_vbtn_input_setup(struct platform_device *device)
+{
+ struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+ int ret;
+
+ priv->buttons_dev = devm_input_allocate_device(&device->dev);
+ if (!priv->buttons_dev)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL);
+ if (ret)
+ return ret;
+
+ priv->buttons_dev->dev.parent = &device->dev;
+ priv->buttons_dev->name = "Intel Virtual Buttons";
+ priv->buttons_dev->id.bustype = BUS_HOST;
+
+ if (priv->has_buttons) {
+ ret = input_register_device(priv->buttons_dev);
+ if (ret)
+ return ret;
+ }
+
+ priv->switches_dev = devm_input_allocate_device(&device->dev);
+ if (!priv->switches_dev)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL);
+ if (ret)
+ return ret;
+
+ priv->switches_dev->dev.parent = &device->dev;
+ priv->switches_dev->name = "Intel Virtual Switches";
+ priv->switches_dev->id.bustype = BUS_HOST;
+
+ if (priv->has_switches) {
+ detect_tablet_mode(device);
+
+ ret = input_register_device(priv->switches_dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void notify_handler(acpi_handle handle, u32 event, void *context)
+{
+ struct platform_device *device = context;
+ struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev);
+ unsigned int val = !(event & 1); /* Even=press, Odd=release */
+ const struct key_entry *ke, *ke_rel;
+ struct input_dev *input_dev;
+ bool autorelease;
+ int ret;
+
+ if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) {
+ if (!priv->has_buttons) {
+ dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n");
+ return;
+ }
+ input_dev = priv->buttons_dev;
+ } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) {
+ if (!priv->has_switches) {
+ /* See dual_accel_detect.h for more info */
+ if (priv->dual_accel)
+ return;
+
+ dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n");
+ ret = input_register_device(priv->switches_dev);
+ if (ret)
+ return;
+
+ priv->has_switches = true;
+ }
+ input_dev = priv->switches_dev;
+ } else {
+ dev_dbg(&device->dev, "unknown event index 0x%x\n", event);
+ return;
+ }
+
+ if (priv->wakeup_mode) {
+ pm_wakeup_hard_event(&device->dev);
+
+ /*
+ * Skip reporting an evdev event for button wake events,
+ * mirroring how the drivers/acpi/button.c code skips this too.
+ */
+ if (ke->type == KE_KEY)
+ return;
+ }
+
+ /*
+ * Even press events are autorelease if there is no corresponding odd
+ * release event, or if the odd event is KE_IGNORE.
+ */
+ ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1);
+ autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE);
+
+ sparse_keymap_report_event(input_dev, event, val, autorelease);
+}
+
+/*
+ * There are several laptops (non 2-in-1) models out there which support VGBS,
+ * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in
+ * turn causes userspace (libinput) to suppress events from the builtin
+ * keyboard and touchpad, making the laptop essentially unusable.
+ *
+ * Since the problem of wrongly reporting SW_TABLET_MODE=1 in combination
+ * with libinput, leads to a non-usable system. Where as OTOH many people will
+ * not even notice when SW_TABLET_MODE is not being reported, a DMI based allow
+ * list is used here. This list mainly matches on the chassis-type of 2-in-1s.
+ *
+ * There are also some 2-in-1s which use the intel-vbtn ACPI interface to report
+ * SW_TABLET_MODE with a chassis-type of 8 ("Portable") or 10 ("Notebook"),
+ * these are matched on a per model basis, since many normal laptops with a
+ * possible broken VGBS ACPI-method also use these chassis-types.
+ */
+static const struct dmi_system_id dmi_switches_allow_list[] = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
+ },
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion 13 x360 PC"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Switch SA5-271"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7352"),
+ },
+ },
+ {} /* Array terminator */
+};
+
+static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel)
+{
+ unsigned long long vgbs;
+ acpi_status status;
+
+ /* See dual_accel_detect.h for more info */
+ if (dual_accel)
+ return false;
+
+ if (!dmi_check_system(dmi_switches_allow_list))
+ return false;
+
+ status = acpi_evaluate_integer(handle, "VGBS", NULL, &vgbs);
+ return ACPI_SUCCESS(status);
+}
+
+static int intel_vbtn_probe(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ bool dual_accel, has_buttons, has_switches;
+ struct intel_vbtn_priv *priv;
+ acpi_status status;
+ int err;
+
+ dual_accel = dual_accel_detect();
+ has_buttons = acpi_has_method(handle, "VBDL");
+ has_switches = intel_vbtn_has_switches(handle, dual_accel);
+
+ if (!has_buttons && !has_switches) {
+ dev_warn(&device->dev, "failed to read Intel Virtual Button driver\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ dev_set_drvdata(&device->dev, priv);
+
+ priv->dual_accel = dual_accel;
+ priv->has_buttons = has_buttons;
+ priv->has_switches = has_switches;
+
+ err = intel_vbtn_input_setup(device);
+ if (err) {
+ pr_err("Failed to setup Intel Virtual Button\n");
+ return err;
+ }
+
+ status = acpi_install_notify_handler(handle,
+ ACPI_DEVICE_NOTIFY,
+ notify_handler,
+ device);
+ if (ACPI_FAILURE(status))
+ return -EBUSY;
+
+ if (has_buttons) {
+ status = acpi_evaluate_object(handle, "VBDL", NULL, NULL);
+ if (ACPI_FAILURE(status))
+ dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status);
+ }
+
+ device_init_wakeup(&device->dev, true);
+ /*
+ * In order for system wakeup to work, the EC GPE has to be marked as
+ * a wakeup one, so do that here (this setting will persist, but it has
+ * no effect until the wakeup mask is set for the EC GPE).
+ */
+ acpi_ec_mark_gpe_for_wake();
+ return 0;
+}
+
+static int intel_vbtn_remove(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+
+ device_init_wakeup(&device->dev, false);
+ acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler);
+
+ /*
+ * Even if we failed to shut off the event stream, we can still
+ * safely detach from the device.
+ */
+ return 0;
+}
+
+static int intel_vbtn_pm_prepare(struct device *dev)
+{
+ if (device_may_wakeup(dev)) {
+ struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
+
+ priv->wakeup_mode = true;
+ }
+ return 0;
+}
+
+static void intel_vbtn_pm_complete(struct device *dev)
+{
+ struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
+
+ priv->wakeup_mode = false;
+}
+
+static int intel_vbtn_pm_resume(struct device *dev)
+{
+ intel_vbtn_pm_complete(dev);
+ return 0;
+}
+
+static const struct dev_pm_ops intel_vbtn_pm_ops = {
+ .prepare = intel_vbtn_pm_prepare,
+ .complete = intel_vbtn_pm_complete,
+ .resume = intel_vbtn_pm_resume,
+ .restore = intel_vbtn_pm_resume,
+ .thaw = intel_vbtn_pm_resume,
+};
+
+static struct platform_driver intel_vbtn_pl_driver = {
+ .driver = {
+ .name = "intel-vbtn",
+ .acpi_match_table = intel_vbtn_ids,
+ .pm = &intel_vbtn_pm_ops,
+ },
+ .probe = intel_vbtn_probe,
+ .remove = intel_vbtn_remove,
+};
+
+static acpi_status __init
+check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+ const struct acpi_device_id *ids = context;
+ struct acpi_device *dev;
+
+ if (acpi_bus_get_device(handle, &dev) != 0)
+ return AE_OK;
+
+ if (acpi_match_device_ids(dev, ids) == 0)
+ if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
+ dev_info(&dev->dev,
+ "intel-vbtn: created platform device\n");
+
+ return AE_OK;
+}
+
+static int __init intel_vbtn_init(void)
+{
+ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX, check_acpi_dev, NULL,
+ (void *)intel_vbtn_ids, NULL);
+
+ return platform_driver_register(&intel_vbtn_pl_driver);
+}
+module_init(intel_vbtn_init);
+
+static void __exit intel_vbtn_exit(void)
+{
+ platform_driver_unregister(&intel_vbtn_pl_driver);
+}
+module_exit(intel_vbtn_exit);
diff --git a/drivers/platform/x86/intel/wmi/Kconfig b/drivers/platform/x86/intel/wmi/Kconfig
new file mode 100644
index 000000000000..8e159f712179
--- /dev/null
+++ b/drivers/platform/x86/intel/wmi/Kconfig
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+config INTEL_WMI
+ bool
+
+config INTEL_WMI_SBL_FW_UPDATE
+ tristate "Intel WMI Slim Bootloader firmware update signaling driver"
+ depends on ACPI_WMI
+ select INTEL_WMI
+ help
+ Say Y here if you want to be able to use the WMI interface to signal
+ Slim Bootloader to trigger update on next reboot.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel-wmi-sbl-fw-update.
+
+config INTEL_WMI_THUNDERBOLT
+ tristate "Intel WMI thunderbolt force power driver"
+ depends on ACPI_WMI
+ select INTEL_WMI
+ help
+ Say Y here if you want to be able to use the WMI interface on select
+ systems to force the power control of Intel Thunderbolt controllers.
+ This is useful for updating the firmware when devices are not plugged
+ into the controller.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel-wmi-thunderbolt.
diff --git a/drivers/platform/x86/intel/wmi/Makefile b/drivers/platform/x86/intel/wmi/Makefile
new file mode 100644
index 000000000000..c2d56d25dea0
--- /dev/null
+++ b/drivers/platform/x86/intel/wmi/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Intel x86 Platform Specific Drivers
+#
+
+intel-wmi-sbl-fw-update-y := sbl-fw-update.o
+obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o
+intel-wmi-thunderbolt-y := thunderbolt.o
+obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o
diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
new file mode 100644
index 000000000000..3c86e0108a24
--- /dev/null
+++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Slim Bootloader(SBL) firmware update signaling driver
+ *
+ * Slim Bootloader is a small, open-source, non UEFI compliant, boot firmware
+ * optimized for running on certain Intel platforms.
+ *
+ * SBL exposes an ACPI-WMI device via /sys/bus/wmi/devices/<INTEL_WMI_SBL_GUID>.
+ * This driver further adds "firmware_update_request" device attribute.
+ * This attribute normally has a value of 0 and userspace can signal SBL
+ * to update firmware, on next reboot, by writing a value of 1.
+ *
+ * More details of SBL firmware update process is available at:
+ * https://slimbootloader.github.io/security/firmware-update.html
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/wmi.h>
+
+#define INTEL_WMI_SBL_GUID "44FADEB1-B204-40F2-8581-394BBDC1B651"
+
+static int get_fwu_request(struct device *dev, u32 *out)
+{
+ struct acpi_buffer result = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_query_block(INTEL_WMI_SBL_GUID, 0, &result);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "wmi_query_block failed\n");
+ return -ENODEV;
+ }
+
+ obj = (union acpi_object *)result.pointer;
+ if (!obj || obj->type != ACPI_TYPE_INTEGER) {
+ dev_warn(dev, "wmi_query_block returned invalid value\n");
+ kfree(obj);
+ return -EINVAL;
+ }
+
+ *out = obj->integer.value;
+ kfree(obj);
+
+ return 0;
+}
+
+static int set_fwu_request(struct device *dev, u32 in)
+{
+ struct acpi_buffer input;
+ acpi_status status;
+ u32 value;
+
+ value = in;
+ input.length = sizeof(u32);
+ input.pointer = &value;
+
+ status = wmi_set_block(INTEL_WMI_SBL_GUID, 0, &input);
+ if (ACPI_FAILURE(status)) {
+ dev_err(dev, "wmi_set_block failed\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static ssize_t firmware_update_request_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u32 val;
+ int ret;
+
+ ret = get_fwu_request(dev, &val);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t firmware_update_request_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ /* May later be extended to support values other than 0 and 1 */
+ if (val > 1)
+ return -ERANGE;
+
+ ret = set_fwu_request(dev, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(firmware_update_request);
+
+static struct attribute *firmware_update_attrs[] = {
+ &dev_attr_firmware_update_request.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(firmware_update);
+
+static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev,
+ const void *context)
+{
+ dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n");
+ return 0;
+}
+
+static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev)
+{
+ dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n");
+}
+
+static const struct wmi_device_id intel_wmi_sbl_id_table[] = {
+ { .guid_string = INTEL_WMI_SBL_GUID },
+ {}
+};
+MODULE_DEVICE_TABLE(wmi, intel_wmi_sbl_id_table);
+
+static struct wmi_driver intel_wmi_sbl_fw_update_driver = {
+ .driver = {
+ .name = "intel-wmi-sbl-fw-update",
+ .dev_groups = firmware_update_groups,
+ },
+ .probe = intel_wmi_sbl_fw_update_probe,
+ .remove = intel_wmi_sbl_fw_update_remove,
+ .id_table = intel_wmi_sbl_id_table,
+};
+module_wmi_driver(intel_wmi_sbl_fw_update_driver);
+
+MODULE_AUTHOR("Jithu Joseph <jithu.joseph@intel.com>");
+MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c
new file mode 100644
index 000000000000..4ae87060d18b
--- /dev/null
+++ b/drivers/platform/x86/intel/wmi/thunderbolt.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WMI Thunderbolt driver
+ *
+ * Copyright (C) 2017 Dell Inc. All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341"
+
+static ssize_t force_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_buffer input;
+ acpi_status status;
+ u8 mode;
+
+ input.length = sizeof(u8);
+ input.pointer = &mode;
+ mode = hex_to_bin(buf[0]);
+ dev_dbg(dev, "force_power: storing %#x\n", mode);
+ if (mode == 0 || mode == 1) {
+ status = wmi_evaluate_method(INTEL_WMI_THUNDERBOLT_GUID, 0, 1,
+ &input, NULL);
+ if (ACPI_FAILURE(status)) {
+ dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
+ return -ENODEV;
+ }
+ } else {
+ dev_dbg(dev, "force_power: unsupported mode\n");
+ return -EINVAL;
+ }
+ return count;
+}
+
+static DEVICE_ATTR_WO(force_power);
+
+static struct attribute *tbt_attrs[] = {
+ &dev_attr_force_power.attr,
+ NULL
+};
+
+static const struct attribute_group tbt_attribute_group = {
+ .attrs = tbt_attrs,
+};
+
+static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev,
+ const void *context)
+{
+ int ret;
+
+ ret = sysfs_create_group(&wdev->dev.kobj, &tbt_attribute_group);
+ kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE);
+ return ret;
+}
+
+static void intel_wmi_thunderbolt_remove(struct wmi_device *wdev)
+{
+ sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group);
+ kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE);
+}
+
+static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = {
+ { .guid_string = INTEL_WMI_THUNDERBOLT_GUID },
+ { },
+};
+
+static struct wmi_driver intel_wmi_thunderbolt_driver = {
+ .driver = {
+ .name = "intel-wmi-thunderbolt",
+ },
+ .probe = intel_wmi_thunderbolt_probe,
+ .remove = intel_wmi_thunderbolt_remove,
+ .id_table = intel_wmi_thunderbolt_id_table,
+};
+
+module_wmi_driver(intel_wmi_thunderbolt_driver);
+
+MODULE_DEVICE_TABLE(wmi, intel_wmi_thunderbolt_id_table);
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
+MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver");
+MODULE_LICENSE("GPL v2");