From e184b1e589a7fbb80bfdd0364c11422999a17a26 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 27 Jul 2021 09:49:28 -0700 Subject: platform/x86/intel: Move Intel PMT drivers to new subfolder Move all Intel Platform Monitoring Technology drivers to drivers/platform/x86/intel/pmt. Signed-off-by: David E. Box Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210727164928.3171521-1-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 36 --- drivers/platform/x86/Makefile | 3 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/pmt/Kconfig | 40 ++++ drivers/platform/x86/intel/pmt/Makefile | 12 + drivers/platform/x86/intel/pmt/class.c | 344 +++++++++++++++++++++++++++++ drivers/platform/x86/intel/pmt/class.h | 53 +++++ drivers/platform/x86/intel/pmt/crashlog.c | 327 +++++++++++++++++++++++++++ drivers/platform/x86/intel/pmt/telemetry.c | 140 ++++++++++++ drivers/platform/x86/intel_pmt_class.c | 344 ----------------------------- drivers/platform/x86/intel_pmt_class.h | 53 ----- drivers/platform/x86/intel_pmt_crashlog.c | 327 --------------------------- drivers/platform/x86/intel_pmt_telemetry.c | 140 ------------ 15 files changed, 919 insertions(+), 904 deletions(-) create mode 100644 drivers/platform/x86/intel/pmt/Kconfig create mode 100644 drivers/platform/x86/intel/pmt/Makefile create mode 100644 drivers/platform/x86/intel/pmt/class.c create mode 100644 drivers/platform/x86/intel/pmt/class.h create mode 100644 drivers/platform/x86/intel/pmt/crashlog.c create mode 100644 drivers/platform/x86/intel/pmt/telemetry.c delete mode 100644 drivers/platform/x86/intel_pmt_class.c delete mode 100644 drivers/platform/x86/intel_pmt_class.h delete mode 100644 drivers/platform/x86/intel_pmt_crashlog.c delete mode 100644 drivers/platform/x86/intel_pmt_telemetry.c diff --git a/MAINTAINERS b/MAINTAINERS index a61f4f3b78a9..14dd0045bc78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9494,7 +9494,7 @@ INTEL PMT DRIVER M: "David E. Box" S: Maintained F: drivers/mfd/intel_pmt.c -F: drivers/platform/x86/intel_pmt_* +F: drivers/platform/x86/intel/pmt/ INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT M: Stanislav Yakovlev diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d12db6c316ea..6ad35158ae4e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1208,42 +1208,6 @@ config INTEL_PMC_CORE - Low Power Mode registers (Tigerlake and beyond) - PMC quirks as needed to enable SLPS0/S0ix -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: - - - 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. - config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" help diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 7ee369aab10d..5edfdc2ea7f2 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -129,9 +129,6 @@ obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o -obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o -obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o -obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index f2eef337eb98..6eec084d9bf9 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -18,5 +18,6 @@ if X86_PLATFORM_DRIVERS_INTEL source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" +source "drivers/platform/x86/intel/pmt/Kconfig" endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 0653055942d5..ca0ec2c85b05 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ +obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ 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: + + + 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..019103ee6522 --- /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 +# + +pmt_class-objs += class.o +obj-$(CONFIG_INTEL_PMT_CLASS) += pmt_class.o +pmt_telemetry-objs += telemetry.o +obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o +pmt_crashlog-objs += crashlog.o +obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_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" + */ + +#include +#include +#include +#include + +#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 "); +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 +#include +#include +#include +#include +#include + +/* 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" + */ + +#include +#include +#include +#include +#include +#include + +#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 "); +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..a58843360fbf --- /dev/null +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Telemetry driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" + */ + +#include +#include +#include +#include +#include +#include + +#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)); + + 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 "); +MODULE_DESCRIPTION("Intel PMT Telemetry driver"); +MODULE_ALIAS("platform:" TELEM_DEV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c deleted file mode 100644 index c86ff15b1ed5..000000000000 --- a/drivers/platform/x86/intel_pmt_class.c +++ /dev/null @@ -1,344 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Platform Monitory Technology Telemetry driver - * - * Copyright (c) 2020, Intel Corporation. - * All Rights Reserved. - * - * Author: "Alexander Duyck" - */ - -#include -#include -#include -#include - -#include "intel_pmt_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 "); -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 deleted file mode 100644 index 1337019c2873..000000000000 --- a/drivers/platform/x86/intel_pmt_class.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _INTEL_PMT_CLASS_H -#define _INTEL_PMT_CLASS_H - -#include -#include -#include -#include -#include -#include - -/* 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 deleted file mode 100644 index 56963ceb6345..000000000000 --- a/drivers/platform/x86/intel_pmt_crashlog.c +++ /dev/null @@ -1,327 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Platform Monitoring Technology Crashlog driver - * - * Copyright (c) 2020, Intel Corporation. - * All Rights Reserved. - * - * Author: "Alexander Duyck" - */ - -#include -#include -#include -#include -#include -#include - -#include "intel_pmt_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 "); -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 deleted file mode 100644 index 9b95ef050457..000000000000 --- a/drivers/platform/x86/intel_pmt_telemetry.c +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Platform Monitory Technology Telemetry driver - * - * Copyright (c) 2020, Intel Corporation. - * All Rights Reserved. - * - * Author: "David E. Box" - */ - -#include -#include -#include -#include -#include -#include - -#include "intel_pmt_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)); - - 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 "); -MODULE_DESCRIPTION("Intel PMT Telemetry driver"); -MODULE_ALIAS("platform:" TELEM_DEV_NAME); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From d36d4a1d75d2a8bd14ec00d5cb0ce166f6886146 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 27 Jul 2021 09:50:52 -0700 Subject: platform/x86: ISST: Fix optimization with use of numa When numa is used to map CPU to PCI device, the optimized path to read from cached data is not working and still calls _isst_if_get_pci_dev(). The reason is that when caching the mapping, numa information is not available as it is read later. So move the assignment of isst_cpu_info[cpu].numa_node before calling _isst_if_get_pci_dev(). Fixes: aa2ddd242572 ("platform/x86: ISST: Use numa node id for cpu pci dev mapping") Signed-off-by: Srinivas Pandruvada Link: https://lore.kernel.org/r/20210727165052.427238-1-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_speed_select_if/isst_if_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 6f0cc679c8e5..8a4d52a9028d 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -379,6 +379,8 @@ 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 */ @@ -397,7 +399,6 @@ static int isst_if_cpu_online(unsigned int cpu) return ret; } isst_cpu_info[cpu].punit_cpu_id = data; - isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); isst_restore_msr_local(cpu); -- cgit v1.2.3 From 1d18ed5eab2a22d9af8c1cd7d23ea7c8dbb9088a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 2 Aug 2021 14:07:34 +0200 Subject: platform/x86: dell-smbios: Remove unused dmi_system_id table dell-smbios is depended on by dell-laptop and that has this same table + some extra entries for chassis-type 30, 31 and 32. Since dell-laptop will already auto-load based on the DMI table in there (which also is more complete) and since dell-laptop will then bring in the dell-smbios module, the only scenario I can think of where this DMI table inside dell-smbios-smm.c is useful is if users have the dell-laptop module disabled and they want to use the sysfs interface offered by dell-smbios-smm.c. But that is such a corner case, even requiring a custom kernel build, that it does not weigh up against having this duplicate table, which as the current state already shows can only grow stale. Users who do hit this corner-case can always explicitly modprobe / insmod the module. Cc: Mario Limonciello Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210802120734.36732-1-hdegoede@redhat.com --- drivers/platform/x86/dell/dell-smbios-smm.c | 31 ----------------------------- 1 file changed, 31 deletions(-) diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c index 97c52a839a3e..320c032418ac 100644 --- a/drivers/platform/x86/dell/dell-smbios-smm.c +++ b/drivers/platform/x86/dell/dell-smbios-smm.c @@ -24,37 +24,6 @@ static struct calling_interface_buffer *buffer; static struct platform_device *platform_device; static DEFINE_MUTEX(smm_mutex); -static const struct dmi_system_id dell_device_table[] __initconst = { - { - .ident = "Dell laptop", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ - }, - }, - { - .ident = "Dell Computer Corporation", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, dell_device_table); - static void parse_da_table(const struct dmi_header *dm) { struct calling_interface_structure *table = -- cgit v1.2.3 From 560c71d4250f5f32b7952c290ddaf3cd4548a3ec Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Tue, 3 Aug 2021 16:16:00 +0200 Subject: platform/x86: Replace deprecated CPU-hotplug functions. The functions get_online_cpus() and put_online_cpus() have been deprecated during the CPU hotplug rework. They map directly to cpus_read_lock() and cpus_read_unlock(). Replace deprecated CPU-hotplug functions with the official version. The behavior remains unchanged. Cc: Stuart Hayes Cc: Hans de Goede Cc: Mark Gross Cc: platform-driver-x86@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20210803141621.780504-18-bigeasy@linutronix.de Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/dcdbas.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index 28447c180be8..5e63d6225048 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -278,9 +278,9 @@ int dcdbas_smi_request(struct smi_cmd *smi_cmd) } /* SMI requires CPU 0 */ - get_online_cpus(); + cpus_read_lock(); ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); - put_online_cpus(); + cpus_read_unlock(); return ret; } -- cgit v1.2.3 From eddebe6dbe2c961ee7f6dc4a1fafc81212b44177 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 3 Aug 2021 19:32:52 +0300 Subject: platform/surface: surface3_power: Use i2c_acpi_get_i2c_resource() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACPI provides a generic helper to get I²C Serial Bus resources. Use it instead of open coded variant. Signed-off-by: Andy Shevchenko Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20210803163252.60141-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface3_power.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c index dea82aa1abd4..90c1568ea4e0 100644 --- a/drivers/platform/surface/surface3_power.c +++ b/drivers/platform/surface/surface3_power.c @@ -384,13 +384,7 @@ mshw0011_space_handler(u32 function, acpi_physical_address command, if (ACPI_FAILURE(ret)) return ret; - if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { - ret = AE_BAD_PARAMETER; - goto err; - } - - sb = &ares->data.i2c_serial_bus; - if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { + if (!value64 || !i2c_acpi_get_i2c_resource(ares, &sb)) { ret = AE_BAD_PARAMETER; goto err; } -- cgit v1.2.3 From b325d78e78a232fb2019023354dd6b7493bc8760 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 3 Aug 2021 22:25:24 +0300 Subject: platform/surface: aggregator: Use y instead of objs in Makefile The 'objs' is for user space tools, for the kernel modules we should use 'y'. Signed-off-by: Andy Shevchenko Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20210803192524.67031-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/Makefile | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index c8498c41e758..c0d550eda5cd 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -6,12 +6,9 @@ CFLAGS_core.o = -I$(src) obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o -surface_aggregator-objs := core.o -surface_aggregator-objs += ssh_parser.o -surface_aggregator-objs += ssh_packet_layer.o -surface_aggregator-objs += ssh_request_layer.o -surface_aggregator-objs += controller.o - -ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) -surface_aggregator-objs += bus.o -endif +surface_aggregator-y := core.o +surface_aggregator-y += ssh_parser.o +surface_aggregator-y += ssh_packet_layer.o +surface_aggregator-y += ssh_request_layer.o +surface_aggregator-$(CONFIG_SURFACE_AGGREGATOR_BUS) += bus.o +surface_aggregator-y += controller.o -- cgit v1.2.3 From bc6b8d7eec4fecb11634bc4256b4b79d0fe617e9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 3 Aug 2021 22:40:39 +0300 Subject: platform/x86: dell-smo8800: Convert to be a platform driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACPI core in conjunction with platform driver core provides an infrastructure to enumerate ACPI devices. Use it in order to remove a lot of boilerplate code. Signed-off-by: Andy Shevchenko Reviewed-by: Pali Rohár Tested-by: Pali Rohár Link: https://lore.kernel.org/r/20210803194039.35083-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/Kconfig | 2 +- drivers/platform/x86/dell/dell-smo8800.c | 74 ++++++++------------------------ 2 files changed, 20 insertions(+), 56 deletions(-) diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index 9e7314d90bea..821aba31821c 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -140,7 +140,7 @@ config DELL_SMBIOS_SMM config DELL_SMO8800 tristate "Dell Latitude freefall driver (ACPI SMO88XX)" default m - depends on ACPI + depends on ACPI || COMPILE_TEST help Say Y here if you want to support SMO88XX freefall devices on Dell Latitude laptops. diff --git a/drivers/platform/x86/dell/dell-smo8800.c b/drivers/platform/x86/dell/dell-smo8800.c index 5d9304a7de1b..3385e852104c 100644 --- a/drivers/platform/x86/dell/dell-smo8800.c +++ b/drivers/platform/x86/dell/dell-smo8800.c @@ -10,13 +10,14 @@ #define DRIVER_NAME "smo8800" -#include -#include -#include +#include #include +#include #include +#include +#include +#include #include -#include struct smo8800_device { u32 irq; /* acpi device irq */ @@ -44,37 +45,6 @@ static irqreturn_t smo8800_interrupt_thread(int irq, void *data) return IRQ_HANDLED; } -static acpi_status smo8800_get_resource(struct acpi_resource *resource, - void *context) -{ - struct acpi_resource_extended_irq *irq; - - if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) - return AE_OK; - - irq = &resource->data.extended_irq; - if (!irq || !irq->interrupt_count) - return AE_OK; - - *((u32 *)context) = irq->interrupts[0]; - return AE_CTRL_TERMINATE; -} - -static u32 smo8800_get_irq(struct acpi_device *device) -{ - u32 irq = 0; - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - smo8800_get_resource, &irq); - if (ACPI_FAILURE(status)) { - dev_err(&device->dev, "acpi_walk_resources failed\n"); - return 0; - } - - return irq; -} - static ssize_t smo8800_misc_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { @@ -136,7 +106,7 @@ static const struct file_operations smo8800_misc_fops = { .release = smo8800_misc_release, }; -static int smo8800_add(struct acpi_device *device) +static int smo8800_probe(struct platform_device *device) { int err; struct smo8800_device *smo8800; @@ -160,14 +130,12 @@ static int smo8800_add(struct acpi_device *device) return err; } - device->driver_data = smo8800; + platform_set_drvdata(device, smo8800); - smo8800->irq = smo8800_get_irq(device); - if (!smo8800->irq) { - dev_err(&device->dev, "failed to obtain IRQ\n"); - err = -EINVAL; + err = platform_get_irq(device, 0); + if (err < 0) goto error; - } + smo8800->irq = err; err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, smo8800_interrupt_thread, @@ -189,9 +157,9 @@ error: return err; } -static int smo8800_remove(struct acpi_device *device) +static int smo8800_remove(struct platform_device *device) { - struct smo8800_device *smo8800 = device->driver_data; + struct smo8800_device *smo8800 = platform_get_drvdata(device); free_irq(smo8800->irq, smo8800); misc_deregister(&smo8800->miscdev); @@ -211,21 +179,17 @@ static const struct acpi_device_id smo8800_ids[] = { { "SMO8831", 0 }, { "", 0 }, }; - MODULE_DEVICE_TABLE(acpi, smo8800_ids); -static struct acpi_driver smo8800_driver = { - .name = DRIVER_NAME, - .class = "Latitude", - .ids = smo8800_ids, - .ops = { - .add = smo8800_add, - .remove = smo8800_remove, +static struct platform_driver smo8800_driver = { + .probe = smo8800_probe, + .remove = smo8800_remove, + .driver = { + .name = DRIVER_NAME, + .acpi_match_table = smo8800_ids, }, - .owner = THIS_MODULE, }; - -module_acpi_driver(smo8800_driver); +module_platform_driver(smo8800_driver); MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From bde53eafb9257a5c9d2327a1cb887c7bcbf6c38f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 6 Aug 2021 18:49:41 +0300 Subject: platform/x86/intel: int33fe: Use y instead of objs in Makefile The 'objs' is for user space tools, for the kernel modules we should use 'y'. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210806154941.4491-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/int33fe/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.3 From cb84acd1165cf3b0f79d2ad09388bb263835974a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 6 Aug 2021 18:50:17 +0300 Subject: platform/x86/intel: pmt: Use y instead of objs in Makefile The 'objs' is for user space tools, for the kernel modules we should use 'y'. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210806155017.4633-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmt/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/Makefile b/drivers/platform/x86/intel/pmt/Makefile index 019103ee6522..279e158c7c23 100644 --- a/drivers/platform/x86/intel/pmt/Makefile +++ b/drivers/platform/x86/intel/pmt/Makefile @@ -4,9 +4,9 @@ # Intel Platform Monitoring Technology Drivers # -pmt_class-objs += class.o obj-$(CONFIG_INTEL_PMT_CLASS) += pmt_class.o -pmt_telemetry-objs += telemetry.o +pmt_class-y := class.o obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o -pmt_crashlog-objs += crashlog.o +pmt_telemetry-y := telemetry.o obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o +pmt_crashlog-y := crashlog.o -- cgit v1.2.3 From f6413ba357b7d78a0e2828b51412037447df5d75 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 6 Aug 2021 18:49:51 +0300 Subject: platform/x86/intel: int3472: Use y instead of objs in Makefile The 'objs' is for user space tools, for the kernel modules we should use 'y'. Signed-off-by: Andy Shevchenko Reviewed-by: Daniel Scally Link: https://lore.kernel.org/r/20210806154951.4564-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/int3472/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -- cgit v1.2.3 From 636a1e697555e73c28cdd6952a409edbfdd16475 Mon Sep 17 00:00:00 2001 From: Chris Blake Date: Mon, 9 Aug 2021 19:40:21 -0500 Subject: platform/x86: add meraki-mx100 platform driver This adds platform support for the Cisco Meraki MX100 (Tinkerbell) network appliance. This sets up the network LEDs and Reset button. Depends-on: ef0eea5b151ae ("mfd: lpc_ich: Enable GPIO driver for DH89xxCC") Co-developed-by: Christian Lamparter Signed-off-by: Christian Lamparter Signed-off-by: Chris Blake Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210810004021.2538308-1-chrisrblake93@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 13 ++ drivers/platform/x86/Makefile | 3 + drivers/platform/x86/meraki-mx100.c | 230 ++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 drivers/platform/x86/meraki-mx100.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6ad35158ae4e..432d72170b00 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -302,6 +302,19 @@ config ASUS_NB_WMI If you have an ACPI-WMI compatible Asus Notebook, say Y or M here. +config MERAKI_MX100 + tristate "Cisco Meraki MX100 Platform Driver" + depends on GPIOLIB + depends on GPIO_ICH + depends on LEDS_CLASS + select LEDS_GPIO + help + This driver provides support for the front button and LEDs on + the Cisco Meraki MX100 (Tinkerbell) 1U appliance. + + To compile this driver as a module, choose M here: the module + will be called meraki-mx100. + config EEEPC_LAPTOP tristate "Eee PC Hotkey Driver" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 5edfdc2ea7f2..9bb3c3f77386 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -39,6 +39,9 @@ obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o +# Cisco/Meraki +obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o + # Dell obj-$(CONFIG_X86_PLATFORM_DRIVERS_DELL) += dell/ diff --git a/drivers/platform/x86/meraki-mx100.c b/drivers/platform/x86/meraki-mx100.c new file mode 100644 index 000000000000..3751ed36a980 --- /dev/null +++ b/drivers/platform/x86/meraki-mx100.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Cisco Meraki MX100 (Tinkerbell) board platform driver + * + * Based off of arch/x86/platform/meraki/tink.c from the + * Meraki GPL release meraki-firmware-sources-r23-20150601 + * + * Format inspired by platform/x86/pcengines-apuv2.c + * + * Copyright (C) 2021 Chris Blake + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TINK_GPIO_DRIVER_NAME "gpio_ich" + +/* LEDs */ +static const struct gpio_led tink_leds[] = { + { + .name = "mx100:green:internet", + .default_trigger = "default-on", + }, + { + .name = "mx100:green:lan2", + }, + { + .name = "mx100:green:lan3", + }, + { + .name = "mx100:green:lan4", + }, + { + .name = "mx100:green:lan5", + }, + { + .name = "mx100:green:lan6", + }, + { + .name = "mx100:green:lan7", + }, + { + .name = "mx100:green:lan8", + }, + { + .name = "mx100:green:lan9", + }, + { + .name = "mx100:green:lan10", + }, + { + .name = "mx100:green:lan11", + }, + { + .name = "mx100:green:ha", + }, + { + .name = "mx100:orange:ha", + }, + { + .name = "mx100:green:usb", + }, + { + .name = "mx100:orange:usb", + }, +}; + +static const struct gpio_led_platform_data tink_leds_pdata = { + .num_leds = ARRAY_SIZE(tink_leds), + .leds = tink_leds, +}; + +static struct gpiod_lookup_table tink_leds_table = { + .dev_id = "leds-gpio", + .table = { + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11, + NULL, 0, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18, + NULL, 1, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20, + NULL, 2, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22, + NULL, 3, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23, + NULL, 4, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32, + NULL, 5, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34, + NULL, 6, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35, + NULL, 7, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36, + NULL, 8, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37, + NULL, 9, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48, + NULL, 10, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16, + NULL, 11, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7, + NULL, 12, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21, + NULL, 13, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19, + NULL, 14, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + } +}; + +/* Reset Button */ +static struct gpio_keys_button tink_buttons[] = { + { + .desc = "Reset", + .type = EV_KEY, + .code = KEY_RESTART, + .active_low = 1, + .debounce_interval = 100, + }, +}; + +static const struct gpio_keys_platform_data tink_buttons_pdata = { + .buttons = tink_buttons, + .nbuttons = ARRAY_SIZE(tink_buttons), + .poll_interval = 20, + .rep = 0, + .name = "mx100-keys", +}; + +static struct gpiod_lookup_table tink_keys_table = { + .dev_id = "gpio-keys-polled", + .table = { + GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60, + NULL, 0, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + } +}; + +/* Board setup */ +static const struct dmi_system_id tink_systems[] __initconst = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Cisco"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MX100-HW"), + }, + }, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(dmi, tink_systems); + +static struct platform_device *tink_leds_pdev; +static struct platform_device *tink_keys_pdev; + +static struct platform_device * __init tink_create_dev( + const char *name, const void *pdata, size_t sz) +{ + struct platform_device *pdev; + + pdev = platform_device_register_data(NULL, + name, PLATFORM_DEVID_NONE, pdata, sz); + if (IS_ERR(pdev)) + pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev)); + + return pdev; +} + +static int __init tink_board_init(void) +{ + int ret; + + if (!dmi_first_match(tink_systems)) + return -ENODEV; + + /* + * We need to make sure that GPIO60 isn't set to native mode as is default since it's our + * Reset Button. To do this, write to GPIO_USE_SEL2 to have GPIO60 set to GPIO mode. + * This is documented on page 1609 of the PCH datasheet, order number 327879-005US + */ + outl(inl(0x530) | BIT(28), 0x530); + + gpiod_add_lookup_table(&tink_leds_table); + gpiod_add_lookup_table(&tink_keys_table); + + tink_leds_pdev = tink_create_dev("leds-gpio", + &tink_leds_pdata, sizeof(tink_leds_pdata)); + if (IS_ERR(tink_leds_pdev)) { + ret = PTR_ERR(tink_leds_pdev); + goto err; + } + + tink_keys_pdev = tink_create_dev("gpio-keys-polled", + &tink_buttons_pdata, sizeof(tink_buttons_pdata)); + if (IS_ERR(tink_keys_pdev)) { + ret = PTR_ERR(tink_keys_pdev); + platform_device_unregister(tink_leds_pdev); + goto err; + } + + return 0; + +err: + gpiod_remove_lookup_table(&tink_keys_table); + gpiod_remove_lookup_table(&tink_leds_table); + return ret; +} +module_init(tink_board_init); + +static void __exit tink_board_exit(void) +{ + platform_device_unregister(tink_keys_pdev); + platform_device_unregister(tink_leds_pdev); + gpiod_remove_lookup_table(&tink_keys_table); + gpiod_remove_lookup_table(&tink_leds_table); +} +module_exit(tink_board_exit); + +MODULE_AUTHOR("Chris Blake "); +MODULE_DESCRIPTION("Cisco Meraki MX100 Platform Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:meraki-mx100"); -- cgit v1.2.3 From 6be70ccdd989e40af151ce52db5b2d93e97fc9fb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 12 Aug 2021 16:55:12 +0200 Subject: platform/x86: asus-nb-wmi: Allow configuring SW_TABLET_MODE method with a module option Unfortunately we have been unable to find a reliable way to detect if and how SW_TABLET_MODE reporting is supported, so we are relying on DMI quirks for this. Add a module-option to specify the SW_TABLET_MODE method so that this can be easily tested without needing to rebuild the kernel. BugLink: https://gitlab.freedesktop.org/libinput/libinput/-/issues/639 Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210812145513.39117-1-hdegoede@redhat.com --- drivers/platform/x86/asus-nb-wmi.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 0cb927f0f301..9929eedf7dd8 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -41,6 +41,10 @@ static int wapf = -1; module_param(wapf, uint, 0444); MODULE_PARM_DESC(wapf, "WAPF value"); +static int tablet_mode_sw = -1; +module_param(tablet_mode_sw, uint, 0444); +MODULE_PARM_DESC(tablet_mode_sw, "Tablet mode detect: -1:auto 0:disable 1:kbd-dock 2:lid-flip"); + static struct quirk_entry *quirks; static bool asus_q500a_i8042_filter(unsigned char data, unsigned char str, @@ -477,6 +481,21 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver) else wapf = quirks->wapf; + switch (tablet_mode_sw) { + case 0: + quirks->use_kbd_dock_devid = false; + quirks->use_lid_flip_devid = false; + break; + case 1: + quirks->use_kbd_dock_devid = true; + quirks->use_lid_flip_devid = false; + break; + case 2: + quirks->use_kbd_dock_devid = false; + quirks->use_lid_flip_devid = true; + break; + } + if (quirks->i8042_filter) { ret = i8042_install_filter(quirks->i8042_filter); if (ret) { -- cgit v1.2.3 From 411f48bb58f49c40a627b052402a90e8301cd07e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 12 Aug 2021 16:55:13 +0200 Subject: platform/x86: asus-nb-wmi: Add tablet_mode_sw=lid-flip quirk for the TP200s The Asus TP200s / E205SA 360 degree hinges 2-in-1 supports reporting SW_TABLET_MODE info through the ASUS_WMI_DEVID_LID_FLIP WMI device-id. Add a quirk to enable this. BugLink: https://gitlab.freedesktop.org/libinput/libinput/-/issues/639 Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210812145513.39117-2-hdegoede@redhat.com --- drivers/platform/x86/asus-nb-wmi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 9929eedf7dd8..a81dc4b191b7 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -462,6 +462,15 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_use_lid_flip_devid, }, + { + .callback = dmi_matched, + .ident = "ASUS TP200s / E205SA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "E205SA"), + }, + .driver_data = &quirk_asus_use_lid_flip_devid, + }, {}, }; -- cgit v1.2.3 From ca91ea34778f9b2a44a391b10164bcd73b4b0f25 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Sat, 7 Aug 2021 14:36:54 +1200 Subject: asus-wmi: Add panel overdrive functionality Some ASUS ROG laptops have the ability to drive the display panel a higher rate to eliminate or reduce ghosting. Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210807023656.25020-2-luke@ljones.dev Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 92 ++++++++++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 1 + 2 files changed, 93 insertions(+) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index ebaeb7bb80f5..cbf91a9134fd 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -216,6 +216,9 @@ struct asus_wmi { // The RSOC controls the maximum charging percentage. bool battery_rsoc_available; + bool panel_overdrive_available; + bool panel_overdrive; + struct hotplug_slot hotplug_slot; struct mutex hotplug_lock; struct mutex wmi_lock; @@ -1221,6 +1224,87 @@ exit: return result; } +/* Panel Overdrive ************************************************************/ +static int panel_od_check_present(struct asus_wmi *asus) +{ + u32 result; + int err; + + asus->panel_overdrive_available = false; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_PANEL_OD, &result); + if (err) { + if (err == -ENODEV) + return 0; + return err; + } + + if (result & ASUS_WMI_DSTS_PRESENCE_BIT) { + asus->panel_overdrive_available = true; + asus->panel_overdrive = result & ASUS_WMI_DSTS_STATUS_BIT; + } + + return 0; +} + +static int panel_od_write(struct asus_wmi *asus) +{ + u32 retval; + u8 value; + int err; + + /* Don't rely on type conversion */ + value = asus->panel_overdrive ? 1 : 0; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PANEL_OD, value, &retval); + + if (err) { + pr_warn("Failed to set panel overdrive: %d\n", err); + return err; + } + + if (retval > 1 || retval < 0) { + pr_warn("Failed to set panel overdrive (retval): 0x%x\n", retval); + return -EIO; + } + + sysfs_notify(&asus->platform_device->dev.kobj, NULL, "panel_od"); + + return 0; +} + +static ssize_t panel_od_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", asus->panel_overdrive); +} + +static ssize_t panel_od_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + bool overdrive; + int result; + + struct asus_wmi *asus = dev_get_drvdata(dev); + + result = kstrtobool(buf, &overdrive); + if (result) + return result; + + asus->panel_overdrive = overdrive; + result = panel_od_write(asus); + + if (result) + return result; + + return count; +} + +static DEVICE_ATTR_RW(panel_od); + /* Quirks *********************************************************************/ static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) @@ -2332,6 +2416,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_als_enable.attr, &dev_attr_fan_boost_mode.attr, &dev_attr_throttle_thermal_policy.attr, + &dev_attr_panel_od.attr, NULL }; @@ -2357,6 +2442,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, ok = asus->fan_boost_mode_available; else if (attr == &dev_attr_throttle_thermal_policy.attr) ok = asus->throttle_thermal_policy_available; + else if (attr == &dev_attr_panel_od.attr) + ok = asus->panel_overdrive_available; if (devid != -1) ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); @@ -2622,6 +2709,10 @@ static int asus_wmi_add(struct platform_device *pdev) else throttle_thermal_policy_set_default(asus); + err = panel_od_check_present(asus); + if (err) + goto fail_panel_od; + err = asus_wmi_sysfs_init(asus->platform_device); if (err) goto fail_sysfs; @@ -2709,6 +2800,7 @@ fail_sysfs: fail_throttle_thermal_policy: fail_fan_boost_mode: fail_platform: +fail_panel_od: kfree(asus); return err; } diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 2f274cf52805..428aea701c7b 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -61,6 +61,7 @@ #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075 /* Misc */ +#define ASUS_WMI_DEVID_PANEL_OD 0x00050019 #define ASUS_WMI_DEVID_CAMERA 0x00060013 #define ASUS_WMI_DEVID_LID_FLIP 0x00060062 -- cgit v1.2.3 From 98829e84dc67630efb7de675f0a70066620468a3 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Sat, 7 Aug 2021 14:36:55 +1200 Subject: asus-wmi: Add dgpu disable method In Windows the ASUS Armory Crate program can enable or disable the dGPU via a WMI call. This functions much the same as various Linux methods in software where the dGPU is removed from the device tree. However the WMI call saves the state of dGPU (enabled or not) and this then changes the dGPU visibility in Linux with no way for Linux users to re-enable it. We expose the WMI method so users can see and change the dGPU ACPI state. Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210807023656.25020-3-luke@ljones.dev Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 98 ++++++++++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 3 + 2 files changed, 101 insertions(+) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index cbf91a9134fd..bee22a12bf3d 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -210,6 +210,9 @@ struct asus_wmi { u8 fan_boost_mode_mask; u8 fan_boost_mode; + bool dgpu_disable_available; + bool dgpu_disable; + bool throttle_thermal_policy_available; u8 throttle_thermal_policy_mode; @@ -427,6 +430,93 @@ static void lid_flip_tablet_mode_get_state(struct asus_wmi *asus) } } +/* dGPU ********************************************************************/ +static int dgpu_disable_check_present(struct asus_wmi *asus) +{ + u32 result; + int err; + + asus->dgpu_disable_available = false; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_DGPU, &result); + if (err) { + if (err == -ENODEV) + return 0; + return err; + } + + if (result & ASUS_WMI_DSTS_PRESENCE_BIT) { + asus->dgpu_disable_available = true; + asus->dgpu_disable = result & ASUS_WMI_DSTS_STATUS_BIT; + } + + return 0; +} + +static int dgpu_disable_write(struct asus_wmi *asus) +{ + u32 retval; + u8 value; + int err; + + /* Don't rely on type conversion */ + value = asus->dgpu_disable ? 1 : 0; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, value, &retval); + if (err) { + pr_warn("Failed to set dgpu disable: %d\n", err); + return err; + } + + if (retval > 1 || retval < 0) { + pr_warn("Failed to set dgpu disable (retval): 0x%x\n", retval); + return -EIO; + } + + sysfs_notify(&asus->platform_device->dev.kobj, NULL, "dgpu_disable"); + + return 0; +} + +static ssize_t dgpu_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + u8 mode = asus->dgpu_disable; + + return sysfs_emit(buf, "%d\n", mode); +} + +/* + * A user may be required to store the value twice, typcial store first, then + * rescan PCI bus to activate power, then store a second time to save correctly. + * The reason for this is that an extra code path in the ACPI is enabled when + * the device and bus are powered. + */ +static ssize_t dgpu_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + bool disable; + int result; + + struct asus_wmi *asus = dev_get_drvdata(dev); + + result = kstrtobool(buf, &disable); + if (result) + return result; + + asus->dgpu_disable = disable; + + result = dgpu_disable_write(asus); + if (result) + return result; + + return count; +} + +static DEVICE_ATTR_RW(dgpu_disable); + /* Battery ********************************************************************/ /* The battery maximum charging percentage */ @@ -2412,6 +2502,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_camera.attr, &dev_attr_cardr.attr, &dev_attr_touchpad.attr, + &dev_attr_dgpu_disable.attr, &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, &dev_attr_fan_boost_mode.attr, @@ -2438,6 +2529,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_LID_RESUME; else if (attr == &dev_attr_als_enable.attr) devid = ASUS_WMI_DEVID_ALS_ENABLE; + else if (attr == &dev_attr_dgpu_disable.attr) + ok = asus->dgpu_disable_available; else if (attr == &dev_attr_fan_boost_mode.attr) ok = asus->fan_boost_mode_available; else if (attr == &dev_attr_throttle_thermal_policy.attr) @@ -2699,6 +2792,10 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + err = dgpu_disable_check_present(asus); + if (err) + goto fail_dgpu_disable; + err = fan_boost_mode_check_present(asus); if (err) goto fail_fan_boost_mode; @@ -2799,6 +2896,7 @@ fail_input: fail_sysfs: fail_throttle_thermal_policy: fail_fan_boost_mode: +fail_dgpu_disable: fail_platform: fail_panel_od: kfree(asus); diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 428aea701c7b..a528f9d0e4b7 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -90,6 +90,9 @@ /* Keyboard dock */ #define ASUS_WMI_DEVID_KBD_DOCK 0x00120063 +/* dgpu on/off */ +#define ASUS_WMI_DEVID_DGPU 0x00090020 + /* DSTS masks */ #define ASUS_WMI_DSTS_STATUS_BIT 0x00000001 #define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002 -- cgit v1.2.3 From 382b91db8044669d254006df799df9d85d4ad891 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Sat, 7 Aug 2021 14:36:56 +1200 Subject: asus-wmi: Add egpu enable method The X13 Flow laptops can utilise an external GPU. This requires toggling an ACPI method which will first disable the internal dGPU, and then enable the eGPU. Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210807023656.25020-4-luke@ljones.dev Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 99 ++++++++++++++++++++++++++++++ include/linux/platform_data/x86/asus-wmi.h | 3 + 2 files changed, 102 insertions(+) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index bee22a12bf3d..90a6a0d00deb 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -210,6 +210,9 @@ struct asus_wmi { u8 fan_boost_mode_mask; u8 fan_boost_mode; + bool egpu_enable_available; // 0 = enable + bool egpu_enable; + bool dgpu_disable_available; bool dgpu_disable; @@ -517,6 +520,94 @@ static ssize_t dgpu_disable_store(struct device *dev, static DEVICE_ATTR_RW(dgpu_disable); +/* eGPU ********************************************************************/ +static int egpu_enable_check_present(struct asus_wmi *asus) +{ + u32 result; + int err; + + asus->egpu_enable_available = false; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_EGPU, &result); + if (err) { + if (err == -ENODEV) + return 0; + return err; + } + + if (result & ASUS_WMI_DSTS_PRESENCE_BIT) { + asus->egpu_enable_available = true; + asus->egpu_enable = result & ASUS_WMI_DSTS_STATUS_BIT; + } + + return 0; +} + +static int egpu_enable_write(struct asus_wmi *asus) +{ + u32 retval; + u8 value; + int err; + + /* Don't rely on type conversion */ + value = asus->egpu_enable ? 1 : 0; + + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, value, &retval); + + if (err) { + pr_warn("Failed to set egpu disable: %d\n", err); + return err; + } + + if (retval > 1 || retval < 0) { + pr_warn("Failed to set egpu disable (retval): 0x%x\n", retval); + return -EIO; + } + + sysfs_notify(&asus->platform_device->dev.kobj, NULL, "egpu_enable"); + + return 0; +} + +static ssize_t egpu_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + bool mode = asus->egpu_enable; + + return sysfs_emit(buf, "%d\n", mode); +} + +/* The ACPI call to enable the eGPU also disables the internal dGPU */ +static ssize_t egpu_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + bool enable; + int result; + + struct asus_wmi *asus = dev_get_drvdata(dev); + + result = kstrtobool(buf, &enable); + if (result) + return result; + + asus->egpu_enable = enable; + + result = egpu_enable_write(asus); + if (result) + return result; + + /* Ensure that the kernel status of dgpu is updated */ + result = dgpu_disable_check_present(asus); + if (result) + return result; + + return count; +} + +static DEVICE_ATTR_RW(egpu_enable); + /* Battery ********************************************************************/ /* The battery maximum charging percentage */ @@ -2502,6 +2593,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_camera.attr, &dev_attr_cardr.attr, &dev_attr_touchpad.attr, + &dev_attr_egpu_enable.attr, &dev_attr_dgpu_disable.attr, &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, @@ -2529,6 +2621,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_LID_RESUME; else if (attr == &dev_attr_als_enable.attr) devid = ASUS_WMI_DEVID_ALS_ENABLE; + else if (attr == &dev_attr_egpu_enable.attr) + ok = asus->egpu_enable_available; else if (attr == &dev_attr_dgpu_disable.attr) ok = asus->dgpu_disable_available; else if (attr == &dev_attr_fan_boost_mode.attr) @@ -2792,6 +2886,10 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + err = egpu_enable_check_present(asus); + if (err) + goto fail_egpu_enable; + err = dgpu_disable_check_present(asus); if (err) goto fail_dgpu_disable; @@ -2896,6 +2994,7 @@ fail_input: fail_sysfs: fail_throttle_thermal_policy: fail_fan_boost_mode: +fail_egpu_enable: fail_dgpu_disable: fail_platform: fail_panel_od: diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index a528f9d0e4b7..17dc5cb6f3f2 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -90,6 +90,9 @@ /* Keyboard dock */ #define ASUS_WMI_DEVID_KBD_DOCK 0x00120063 +/* dgpu on/off */ +#define ASUS_WMI_DEVID_EGPU 0x00090019 + /* dgpu on/off */ #define ASUS_WMI_DEVID_DGPU 0x00090020 -- cgit v1.2.3 From 20a1b3acfc802ad7b6b327f2bdc0570711538561 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 3 Aug 2021 18:00:41 +0200 Subject: i2c: acpi: Add an i2c_acpi_client_count() helper function We have 3 files now which have the need to count the number of I2cSerialBus resources in an ACPI-device's resource-list. Currently all implement their own helper function for this, add a generic helper function to replace the 3 implementations. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210803160044.158802-2-hdegoede@redhat.com Acked-by: Mika Westerberg Acked-by: Wolfram Sang --- drivers/i2c/i2c-core-acpi.c | 32 ++++++++++++++++++++++++++++++++ include/linux/i2c.h | 5 +++++ 2 files changed, 37 insertions(+) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 6f0aa0ed3241..aaeeacc12121 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -69,6 +69,38 @@ bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, } EXPORT_SYMBOL_GPL(i2c_acpi_get_i2c_resource); +static int i2c_acpi_resource_count(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + int *count = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) + *count = *count + 1; + + return 1; +} + +/** + * i2c_acpi_client_count - Count the number of I2cSerialBus resources + * @adev: ACPI device + * + * Returns the number of I2cSerialBus resources in the ACPI-device's + * resource-list; or a negative error code. + */ +int i2c_acpi_client_count(struct acpi_device *adev) +{ + int ret, count = 0; + LIST_HEAD(r); + + ret = acpi_dev_get_resources(adev, &r, i2c_acpi_resource_count, &count); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&r); + return count; +} +EXPORT_SYMBOL_GPL(i2c_acpi_client_count); + static int i2c_acpi_fill_info(struct acpi_resource *ares, void *data) { struct i2c_acpi_lookup *lookup = data; diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 3eb60a2e9e61..2ce3efbe9198 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -1010,6 +1010,7 @@ struct acpi_resource_i2c_serialbus; #if IS_ENABLED(CONFIG_ACPI) bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, struct acpi_resource_i2c_serialbus **i2c); +int i2c_acpi_client_count(struct acpi_device *adev); u32 i2c_acpi_find_bus_speed(struct device *dev); struct i2c_client *i2c_acpi_new_device(struct device *dev, int index, struct i2c_board_info *info); @@ -1020,6 +1021,10 @@ static inline bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, { return false; } +static inline int i2c_acpi_client_count(struct acpi_device *adev) +{ + return 0; +} static inline u32 i2c_acpi_find_bus_speed(struct device *dev) { return 0; -- cgit v1.2.3 From f13d483eafdf60fc809e555e0329ee6257582612 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 3 Aug 2021 18:00:42 +0200 Subject: platform/x86: dual_accel_detect: Use the new i2c_acpi_client_count() helper Use the new i2c_acpi_client_count() helper, this results in a nice cleanup. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210803160044.158802-3-hdegoede@redhat.com --- drivers/platform/x86/dual_accel_detect.h | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/drivers/platform/x86/dual_accel_detect.h b/drivers/platform/x86/dual_accel_detect.h index a9eae17cc43d..72e9624331c8 100644 --- a/drivers/platform/x86/dual_accel_detect.h +++ b/drivers/platform/x86/dual_accel_detect.h @@ -17,30 +17,6 @@ #include #include -static int dual_accel_i2c_resource_count(struct acpi_resource *ares, void *data) -{ - struct acpi_resource_i2c_serialbus *sb; - int *count = data; - - if (i2c_acpi_get_i2c_resource(ares, &sb)) - *count = *count + 1; - - return 1; -} - -static int dual_accel_i2c_client_count(struct acpi_device *adev) -{ - int ret, count = 0; - LIST_HEAD(r); - - ret = acpi_dev_get_resources(adev, &r, dual_accel_i2c_resource_count, &count); - if (ret < 0) - return ret; - - acpi_dev_free_resource_list(&r); - return count; -} - static bool dual_accel_detect_bosc0200(void) { struct acpi_device *adev; @@ -50,7 +26,7 @@ static bool dual_accel_detect_bosc0200(void) if (!adev) return false; - count = dual_accel_i2c_client_count(adev); + count = i2c_acpi_client_count(adev); acpi_dev_put(adev); -- cgit v1.2.3 From 5791c2648dc468c00de405b2af32c5189d5cb345 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 3 Aug 2021 18:00:43 +0200 Subject: platform/x86: i2c-multi-instantiate: Use the new i2c_acpi_client_count() helper Use the new i2c_acpi_client_count() helper, this results in a nice cleanup. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210803160044.158802-4-hdegoede@redhat.com --- drivers/platform/x86/i2c-multi-instantiate.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c index 2cce82579d09..a50153ecd560 100644 --- a/drivers/platform/x86/i2c-multi-instantiate.c +++ b/drivers/platform/x86/i2c-multi-instantiate.c @@ -32,31 +32,6 @@ struct i2c_multi_inst_data { struct i2c_client *clients[]; }; -static int i2c_multi_inst_count(struct acpi_resource *ares, void *data) -{ - struct acpi_resource_i2c_serialbus *sb; - int *count = data; - - if (i2c_acpi_get_i2c_resource(ares, &sb)) - *count = *count + 1; - - return 1; -} - -static int i2c_multi_inst_count_resources(struct acpi_device *adev) -{ - LIST_HEAD(r); - int count = 0; - int ret; - - ret = acpi_dev_get_resources(adev, &r, i2c_multi_inst_count, &count); - if (ret < 0) - return ret; - - acpi_dev_free_resource_list(&r); - return count; -} - static int i2c_multi_inst_probe(struct platform_device *pdev) { struct i2c_multi_inst_data *multi; @@ -76,7 +51,7 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) adev = ACPI_COMPANION(dev); /* Count number of clients to instantiate */ - ret = i2c_multi_inst_count_resources(adev); + ret = i2c_acpi_client_count(adev); if (ret < 0) return ret; -- cgit v1.2.3 From e4ec7a49ef8bb4edc85a0eee005d59fa65c94a0e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 3 Aug 2021 18:00:44 +0200 Subject: platform/x86: intel_cht_int33fe: Use the new i2c_acpi_client_count() helper Use the new i2c_acpi_client_count() helper, this results in a nice cleanup. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210803160044.158802-5-hdegoede@redhat.com --- .../x86/intel/int33fe/intel_cht_int33fe_common.c | 29 +--------------------- 1 file changed, 1 insertion(+), 28 deletions(-) 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; -- cgit v1.2.3 From 8599a12b1e01039efb13151ff922bc16e5013767 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 10 Aug 2021 18:09:00 +0200 Subject: platform/x86: Update Mario Limonciello's email address in the docs Various pdx86 docs under Documentation/ABI/testing still use Mario's old, now defunct, address. Update the docs to point to either the new Dell.Client.Kernel@dell.com email alias for Dell specific drivers, or to Mario's new @outlook.com address for other drivers. Cc: Dell.Client.Kernel@dell.com Cc: Mario Limonciello Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210810160900.106512-1-hdegoede@redhat.com --- Documentation/ABI/testing/dell-smbios-wmi | 2 +- Documentation/ABI/testing/sysfs-bus-thunderbolt | 2 +- Documentation/ABI/testing/sysfs-class-firmware-attributes | 8 ++++---- Documentation/ABI/testing/sysfs-platform-dell-smbios | 2 +- Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt | 2 +- Documentation/ABI/testing/sysfs-power | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/dell-smbios-wmi b/Documentation/ABI/testing/dell-smbios-wmi index 5f3a0dc67050..f58229084469 100644 --- a/Documentation/ABI/testing/dell-smbios-wmi +++ b/Documentation/ABI/testing/dell-smbios-wmi @@ -1,7 +1,7 @@ What: /dev/wmi/dell-smbios Date: November 2017 KernelVersion: 4.15 -Contact: "Mario Limonciello" +Contact: Dell.Client.Kernel@dell.com Description: Perform SMBIOS calls on supported Dell machines. through the Dell ACPI-WMI interface. diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 95c21d6c9a84..b7e87f6c7d47 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -232,7 +232,7 @@ Description: When new NVM image is written to the non-active NVM What: /sys/bus/thunderbolt/devices/.../nvm_authenticate_on_disconnect Date: Oct 2020 KernelVersion: v5.9 -Contact: Mario Limonciello +Contact: Mario Limonciello Description: For supported devices, automatically authenticate the new Thunderbolt image when the device is disconnected from the host system. diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 3348bf80a37c..0b43997b76e3 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -2,8 +2,8 @@ What: /sys/class/firmware-attributes/*/attributes/*/ Date: February 2021 KernelVersion: 5.11 Contact: Divya Bharathi , - Mario Limonciello , Prasanth KSR + Dell.Client.Kernel@dell.com Description: A sysfs interface for systems management software to enable configuration capability on supported systems. This directory @@ -130,8 +130,8 @@ What: /sys/class/firmware-attributes/*/authentication/ Date: February 2021 KernelVersion: 5.11 Contact: Divya Bharathi , - Mario Limonciello , Prasanth KSR + Dell.Client.Kernel@dell.com Description: Devices support various authentication mechanisms which can be exposed as a separate configuration object. @@ -220,8 +220,8 @@ What: /sys/class/firmware-attributes/*/attributes/pending_reboot Date: February 2021 KernelVersion: 5.11 Contact: Divya Bharathi , - Mario Limonciello , Prasanth KSR + Dell.Client.Kernel@dell.com Description: A read-only attribute reads 1 if a reboot is necessary to apply pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is @@ -249,8 +249,8 @@ What: /sys/class/firmware-attributes/*/attributes/reset_bios Date: February 2021 KernelVersion: 5.11 Contact: Divya Bharathi , - Mario Limonciello , Prasanth KSR + Dell.Client.Kernel@dell.com Description: This attribute can be used to reset the BIOS Configuration. Specifically, it tells which type of reset BIOS configuration is being diff --git a/Documentation/ABI/testing/sysfs-platform-dell-smbios b/Documentation/ABI/testing/sysfs-platform-dell-smbios index e6e0f7f834a7..5583da581025 100644 --- a/Documentation/ABI/testing/sysfs-platform-dell-smbios +++ b/Documentation/ABI/testing/sysfs-platform-dell-smbios @@ -1,7 +1,7 @@ What: /sys/devices/platform//tokens/* Date: November 2017 KernelVersion: 4.15 -Contact: "Mario Limonciello" +Contact: Dell.Client.Kernel@dell.com Description: A read-only description of Dell platform tokens available on the machine. diff --git a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt index e19144fd5d86..fd3a7ec79760 100644 --- a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt +++ b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt @@ -1,7 +1,7 @@ What: /sys/devices/platform//force_power Date: September 2017 KernelVersion: 4.15 -Contact: "Mario Limonciello" +Contact: "Mario Limonciello" Description: Modify the platform force power state, influencing Thunderbolt controllers to turn on or off when no diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index 51c0f578bfce..90ec4987074b 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -295,7 +295,7 @@ Description: What: /sys/power/resume_offset Date: April 2018 -Contact: Mario Limonciello +Contact: Mario Limonciello Description: This file is used for telling the kernel an offset into a disk to use when hibernating the system such as with a swap file. -- cgit v1.2.3 From ca42c119fc6746e65423257e7eddf5fc9e96edc2 Mon Sep 17 00:00:00 2001 From: JafarAkhondali Date: Thu, 12 Aug 2021 17:23:07 +0430 Subject: platform/x86: acer-wmi: Add Turbo Mode support for Acer PH315-53 The Acer Predator Helios series (usually denoted by PHxxx-yy) features a particular key above the keyboard named "TURBO". The turbo key does 3 things: 1. Set all fan's speeds to TURBO mode 2. Overclocks the CPU and GPU in the safe range 3. Turn on an LED just below the turbo button All the above actions are operating using WMI function calls, and there is no custom OC level for turbo. It acts as a flag for enabling turbo mode instead of telling processors to use a specific multiply of power (e.g. 1.3x of power). I've run some benchmark tests and it worked fine: GpuTest 0.7.0 http://www.geeks3d.com Module: FurMark Normal mode Score: 7289 points (FPS: 121) Turbo mode Score: 7675 points (FPS: 127) Settings: - 1920x1080 fullscreen - antialiasing: Off - duration: 60000 ms Renderer: - GeForce RTX 2060/PCIe/SSE2 - OpenGL: 4.6.0 NVIDIA 460.32.03 This feature is presented by Acer officially and should not harm hardware in any case. A challenging part of implementing this feature is that calling overclock function requires knowing the exact count of fans for CPU and GPU of each model, which to the best of my knowledge is not available in the kernel. So after checking the official PredatorSense application methods, it turned out they have provided the software the list of fans in each model. I have access to the mentioned list, and all similar PH-iii-jj can be added easily by matching "DMI_PRODUCT_NAME". Creating a specific file for the Acer gaming features is not possible because the current in use WMI event GUID is required for the turbo button and it's not possible to register multiple listeners on a single WMI event. Signed-off-by: JafarAkhondali Link: https://lore.kernel.org/r/20210812125307.1749207-1-jafar.akhoondali@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/acer-wmi.c | 179 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 85db9403cc14..694b45ed06a2 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -60,6 +60,11 @@ MODULE_LICENSE("GPL"); #define ACER_WMID_GET_THREEG_METHODID 10 #define ACER_WMID_SET_THREEG_METHODID 11 +#define ACER_WMID_SET_GAMING_LED_METHODID 2 +#define ACER_WMID_GET_GAMING_LED_METHODID 4 +#define ACER_WMID_SET_GAMING_FAN_BEHAVIOR 14 +#define ACER_WMID_SET_GAMING_MISC_SETTING_METHODID 22 + /* * Acer ACPI method GUIDs */ @@ -68,6 +73,7 @@ MODULE_LICENSE("GPL"); #define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3" #define WMID_GUID2 "95764E09-FB56-4E83-B31A-37761F60994A" #define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" +#define WMID_GUID4 "7A4DDFE7-5B5D-40B4-8595-4408E0CC7F56" /* * Acer ACPI event GUIDs @@ -81,6 +87,7 @@ MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026"); enum acer_wmi_event_ids { WMID_HOTKEY_EVENT = 0x1, WMID_ACCEL_OR_KBD_DOCK_EVENT = 0x5, + WMID_GAMING_TURBO_KEY_EVENT = 0x7, }; static const struct key_entry acer_wmi_keymap[] __initconst = { @@ -215,6 +222,9 @@ struct hotkey_function_type_aa { #define ACER_CAP_THREEG BIT(4) #define ACER_CAP_SET_FUNCTION_MODE BIT(5) #define ACER_CAP_KBD_DOCK BIT(6) +#define ACER_CAP_TURBO_OC BIT(7) +#define ACER_CAP_TURBO_LED BIT(8) +#define ACER_CAP_TURBO_FAN BIT(9) /* * Interface type flags @@ -301,6 +311,9 @@ struct quirk_entry { u8 mailled; s8 brightness; u8 bluetooth; + u8 turbo; + u8 cpu_fans; + u8 gpu_fans; }; static struct quirk_entry *quirks; @@ -312,6 +325,10 @@ static void __init set_quirks(void) if (quirks->brightness) interface->capability |= ACER_CAP_BRIGHTNESS; + + if (quirks->turbo) + interface->capability |= ACER_CAP_TURBO_OC | ACER_CAP_TURBO_LED + | ACER_CAP_TURBO_FAN; } static int __init dmi_matched(const struct dmi_system_id *dmi) @@ -340,6 +357,12 @@ static struct quirk_entry quirk_acer_travelmate_2490 = { .mailled = 1, }; +static struct quirk_entry quirk_acer_predator_ph315_53 = { + .turbo = 1, + .cpu_fans = 1, + .gpu_fans = 1, +}; + /* This AMW0 laptop has no bluetooth */ static struct quirk_entry quirk_medion_md_98300 = { .wireless = 1, @@ -507,6 +530,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, .driver_data = &quirk_acer_travelmate_2490, }, + { + .callback = dmi_matched, + .ident = "Acer Predator PH315-53", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Predator PH315-53"), + }, + .driver_data = &quirk_acer_predator_ph315_53, + }, { .callback = set_force_caps, .ident = "Acer Aspire Switch 10E SW3-016", @@ -1344,6 +1376,114 @@ static struct wmi_interface wmid_v2_interface = { .type = ACER_WMID_v2, }; +/* + * WMID Gaming interface + */ + +static acpi_status +WMI_gaming_execute_u64(u32 method_id, u64 in, u64 *out) +{ + struct acpi_buffer input = { (acpi_size) sizeof(u64), (void *)(&in) }; + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + u32 tmp = 0; + acpi_status status; + + status = wmi_evaluate_method(WMID_GUID4, 0, method_id, &input, &result); + + if (ACPI_FAILURE(status)) + return status; + obj = (union acpi_object *) result.pointer; + + if (obj) { + if (obj->type == ACPI_TYPE_BUFFER) { + if (obj->buffer.length == sizeof(u32)) + tmp = *((u32 *) obj->buffer.pointer); + else if (obj->buffer.length == sizeof(u64)) + tmp = *((u64 *) obj->buffer.pointer); + } else if (obj->type == ACPI_TYPE_INTEGER) { + tmp = (u64) obj->integer.value; + } + } + + if (out) + *out = tmp; + + kfree(result.pointer); + + return status; +} + +static acpi_status WMID_gaming_set_u64(u64 value, u32 cap) +{ + u32 method_id = 0; + + if (!(interface->capability & cap)) + return AE_BAD_PARAMETER; + + switch (cap) { + case ACER_CAP_TURBO_LED: + method_id = ACER_WMID_SET_GAMING_LED_METHODID; + break; + case ACER_CAP_TURBO_FAN: + method_id = ACER_WMID_SET_GAMING_FAN_BEHAVIOR; + break; + case ACER_CAP_TURBO_OC: + method_id = ACER_WMID_SET_GAMING_MISC_SETTING_METHODID; + break; + default: + return AE_BAD_PARAMETER; + } + + return WMI_gaming_execute_u64(method_id, value, NULL); +} + +static acpi_status WMID_gaming_get_u64(u64 *value, u32 cap) +{ + acpi_status status; + u64 result; + u64 input; + u32 method_id; + + if (!(interface->capability & cap)) + return AE_BAD_PARAMETER; + + switch (cap) { + case ACER_CAP_TURBO_LED: + method_id = ACER_WMID_GET_GAMING_LED_METHODID; + input = 0x1; + break; + default: + return AE_BAD_PARAMETER; + } + status = WMI_gaming_execute_u64(method_id, input, &result); + if (ACPI_SUCCESS(status)) + *value = (u64) result; + + return status; +} + +static void WMID_gaming_set_fan_mode(u8 fan_mode) +{ + /* fan_mode = 1 is used for auto, fan_mode = 2 used for turbo*/ + u64 gpu_fan_config1 = 0, gpu_fan_config2 = 0; + int i; + + if (quirks->cpu_fans > 0) + gpu_fan_config2 |= 1; + for (i = 0; i < (quirks->cpu_fans + quirks->gpu_fans); ++i) + gpu_fan_config2 |= 1 << (i + 1); + for (i = 0; i < quirks->gpu_fans; ++i) + gpu_fan_config2 |= 1 << (i + 3); + if (quirks->cpu_fans > 0) + gpu_fan_config1 |= fan_mode; + for (i = 0; i < (quirks->cpu_fans + quirks->gpu_fans); ++i) + gpu_fan_config1 |= fan_mode << (2 * i + 2); + for (i = 0; i < quirks->gpu_fans; ++i) + gpu_fan_config1 |= fan_mode << (2 * i + 6); + WMID_gaming_set_u64(gpu_fan_config2 | gpu_fan_config1 << 16, ACER_CAP_TURBO_FAN); +} + /* * Generic Device (interface-independent) */ @@ -1575,6 +1715,41 @@ static int acer_gsensor_event(void) return 0; } +/* + * Predator series turbo button + */ +static int acer_toggle_turbo(void) +{ + u64 turbo_led_state; + + /* Get current state from turbo button */ + if (ACPI_FAILURE(WMID_gaming_get_u64(&turbo_led_state, ACER_CAP_TURBO_LED))) + return -1; + + if (turbo_led_state) { + /* Turn off turbo led */ + WMID_gaming_set_u64(0x1, ACER_CAP_TURBO_LED); + + /* Set FAN mode to auto */ + WMID_gaming_set_fan_mode(0x1); + + /* Set OC to normal */ + WMID_gaming_set_u64(0x5, ACER_CAP_TURBO_OC); + WMID_gaming_set_u64(0x7, ACER_CAP_TURBO_OC); + } else { + /* Turn on turbo led */ + WMID_gaming_set_u64(0x10001, ACER_CAP_TURBO_LED); + + /* Set FAN mode to turbo */ + WMID_gaming_set_fan_mode(0x2); + + /* Set OC to turbo mode */ + WMID_gaming_set_u64(0x205, ACER_CAP_TURBO_OC); + WMID_gaming_set_u64(0x207, ACER_CAP_TURBO_OC); + } + return turbo_led_state; +} + /* * Switch series keyboard dock status */ @@ -1872,6 +2047,10 @@ static void acer_wmi_notify(u32 value, void *context) acer_gsensor_event(); acer_kbd_dock_event(&return_value); break; + case WMID_GAMING_TURBO_KEY_EVENT: + if (return_value.key_num == 0x4) + acer_toggle_turbo(); + break; default: pr_warn("Unknown function number - %d - %d\n", return_value.function, return_value.key_num); -- cgit v1.2.3 From 2010319b3c438ba428e9a5c54998c5b05631cf78 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 15 Aug 2021 20:53:56 -0700 Subject: thermal/drivers/intel: Move intel_menlow to thermal drivers Moved drivers/platform/x86/intel_menlow.c to drivers/thermal/intel. Signed-off-by: Srinivas Pandruvada Acked-by: Daniel Lezcano Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20210816035356.1955982-1-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 4 +- drivers/platform/x86/Kconfig | 10 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_menlow.c | 523 ----------------------------------- drivers/thermal/intel/Kconfig | 9 + drivers/thermal/intel/Makefile | 1 + drivers/thermal/intel/intel_menlow.c | 523 +++++++++++++++++++++++++++++++++++ 7 files changed, 535 insertions(+), 536 deletions(-) delete mode 100644 drivers/platform/x86/intel_menlow.c create mode 100644 drivers/thermal/intel/intel_menlow.c diff --git a/MAINTAINERS b/MAINTAINERS index 14dd0045bc78..279af7e9a281 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9458,10 +9458,10 @@ F: include/linux/mfd/intel-m10-bmc.h INTEL MENLOW THERMAL DRIVER M: Sujith Thomas -L: platform-driver-x86@vger.kernel.org +L: linux-pm@vger.kernel.org S: Supported W: https://01.org/linux-acpi -F: drivers/platform/x86/intel_menlow.c +F: drivers/thermal/intel/intel_menlow.c INTEL P-Unit IPC DRIVER M: Zha Qipeng diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 432d72170b00..a87a4960256d 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -733,16 +733,6 @@ config INTEL_INT0002_VGPIO To compile this driver as a module, choose M here: the module will be called intel_int0002_vgpio. -config INTEL_MENLOW - tristate "Thermal Management driver for Intel menlow platform" - depends on ACPI_THERMAL - select THERMAL - help - ACPI thermal management enhancement driver on - Intel Menlow platform. - - If unsure, say N. - config INTEL_OAKTRAIL tristate "Intel Oaktrail Platform Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9bb3c3f77386..03a1fc20bba5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -75,7 +75,6 @@ obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o -obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c deleted file mode 100644 index 101d7e791a13..000000000000 --- a/drivers/platform/x86/intel_menlow.c +++ /dev/null @@ -1,523 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel menlow Driver for thermal management extension - * - * Copyright (C) 2008 Intel Corp - * Copyright (C) 2008 Sujith Thomas - * Copyright (C) 2008 Zhang Rui - * - * This driver creates the sys I/F for programming the sensors. - * It also implements the driver for intel menlow memory controller (hardware - * id is INT0002) which makes use of the platform specific ACPI methods - * to get/set bandwidth. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_AUTHOR("Thomas Sujith"); -MODULE_AUTHOR("Zhang Rui"); -MODULE_DESCRIPTION("Intel Menlow platform specific driver"); -MODULE_LICENSE("GPL v2"); - -/* - * Memory controller device control - */ - -#define MEMORY_GET_BANDWIDTH "GTHS" -#define MEMORY_SET_BANDWIDTH "STHS" -#define MEMORY_ARG_CUR_BANDWIDTH 1 -#define MEMORY_ARG_MAX_BANDWIDTH 0 - -static void intel_menlow_unregister_sensor(void); - -/* - * GTHS returning 'n' would mean that [0,n-1] states are supported - * In that case max_cstate would be n-1 - * GTHS returning '0' would mean that no bandwidth control states are supported - */ -static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev, - unsigned long *max_state) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - unsigned long long value; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status = AE_OK; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH; - status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, - &arg_list, &value); - if (ACPI_FAILURE(status)) - return -EFAULT; - - if (!value) - return -EINVAL; - - *max_state = value - 1; - return 0; -} - -static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, - unsigned long *value) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - unsigned long long result; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status = AE_OK; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH; - status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, - &arg_list, &result); - if (ACPI_FAILURE(status)) - return -EFAULT; - - *value = result; - return 0; -} - -static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, - unsigned long state) -{ - struct acpi_device *device = cdev->devdata; - acpi_handle handle = device->handle; - struct acpi_object_list arg_list; - union acpi_object arg; - acpi_status status; - unsigned long long temp; - unsigned long max_state; - - if (memory_get_max_bandwidth(cdev, &max_state)) - return -EFAULT; - - if (state > max_state) - return -EINVAL; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = state; - - status = - acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, - &temp); - - pr_info("Bandwidth value was %ld: status is %d\n", state, status); - if (ACPI_FAILURE(status)) - return -EFAULT; - - return 0; -} - -static const struct thermal_cooling_device_ops memory_cooling_ops = { - .get_max_state = memory_get_max_bandwidth, - .get_cur_state = memory_get_cur_bandwidth, - .set_cur_state = memory_set_cur_bandwidth, -}; - -/* - * Memory Device Management - */ -static int intel_menlow_memory_add(struct acpi_device *device) -{ - int result = -ENODEV; - struct thermal_cooling_device *cdev; - - if (!device) - return -EINVAL; - - if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH)) - goto end; - - if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH)) - goto end; - - cdev = thermal_cooling_device_register("Memory controller", device, - &memory_cooling_ops); - if (IS_ERR(cdev)) { - result = PTR_ERR(cdev); - goto end; - } - - device->driver_data = cdev; - result = sysfs_create_link(&device->dev.kobj, - &cdev->device.kobj, "thermal_cooling"); - if (result) - goto unregister; - - result = sysfs_create_link(&cdev->device.kobj, - &device->dev.kobj, "device"); - if (result) { - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - goto unregister; - } - - end: - return result; - - unregister: - thermal_cooling_device_unregister(cdev); - return result; - -} - -static int intel_menlow_memory_remove(struct acpi_device *device) -{ - struct thermal_cooling_device *cdev; - - if (!device) - return -EINVAL; - - cdev = acpi_driver_data(device); - if (!cdev) - return -EINVAL; - - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&cdev->device.kobj, "device"); - thermal_cooling_device_unregister(cdev); - - return 0; -} - -static const struct acpi_device_id intel_menlow_memory_ids[] = { - {"INT0002", 0}, - {"", 0}, -}; - -static struct acpi_driver intel_menlow_memory_driver = { - .name = "intel_menlow_thermal_control", - .ids = intel_menlow_memory_ids, - .ops = { - .add = intel_menlow_memory_add, - .remove = intel_menlow_memory_remove, - }, -}; - -/* - * Sensor control on menlow platform - */ - -#define THERMAL_AUX0 0 -#define THERMAL_AUX1 1 -#define GET_AUX0 "GAX0" -#define GET_AUX1 "GAX1" -#define SET_AUX0 "SAX0" -#define SET_AUX1 "SAX1" - -struct intel_menlow_attribute { - struct device_attribute attr; - struct device *device; - acpi_handle handle; - struct list_head node; -}; - -static LIST_HEAD(intel_menlow_attr_list); -static DEFINE_MUTEX(intel_menlow_attr_lock); - -/* - * sensor_get_auxtrip - get the current auxtrip value from sensor - * @name: Thermalzone name - * @auxtype : AUX0/AUX1 - * @buf: syfs buffer - */ -static int sensor_get_auxtrip(acpi_handle handle, int index, - unsigned long long *value) -{ - acpi_status status; - - if ((index != 0 && index != 1) || !value) - return -EINVAL; - - status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, - NULL, value); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - -/* - * sensor_set_auxtrip - set the new auxtrip value to sensor - * @name: Thermalzone name - * @auxtype : AUX0/AUX1 - * @buf: syfs buffer - */ -static int sensor_set_auxtrip(acpi_handle handle, int index, int value) -{ - acpi_status status; - union acpi_object arg = { - ACPI_TYPE_INTEGER - }; - struct acpi_object_list args = { - 1, &arg - }; - unsigned long long temp; - - if (index != 0 && index != 1) - return -EINVAL; - - status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, - NULL, &temp); - if (ACPI_FAILURE(status)) - return -EIO; - if ((index && value < temp) || (!index && value > temp)) - return -EINVAL; - - arg.integer.value = value; - status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, - &args, &temp); - if (ACPI_FAILURE(status)) - return -EIO; - - /* do we need to check the return value of SAX0/SAX1 ? */ - - return 0; -} - -#define to_intel_menlow_attr(_attr) \ - container_of(_attr, struct intel_menlow_attribute, attr) - -static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr, - char *buf, int idx) -{ - struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - unsigned long long value; - int result; - - result = sensor_get_auxtrip(attr->handle, idx, &value); - if (result) - return result; - - return sprintf(buf, "%lu", deci_kelvin_to_celsius(value)); -} - -static ssize_t aux0_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - return aux_show(dev, dev_attr, buf, 0); -} - -static ssize_t aux1_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) -{ - return aux_show(dev, dev_attr, buf, 1); -} - -static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr, - const char *buf, size_t count, int idx) -{ - struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - int value; - int result; - - /*Sanity check; should be a positive integer */ - if (!sscanf(buf, "%d", &value)) - return -EINVAL; - - if (value < 0) - return -EINVAL; - - result = sensor_set_auxtrip(attr->handle, idx, - celsius_to_deci_kelvin(value)); - return result ? result : count; -} - -static ssize_t aux0_store(struct device *dev, - struct device_attribute *dev_attr, - const char *buf, size_t count) -{ - return aux_store(dev, dev_attr, buf, count, 0); -} - -static ssize_t aux1_store(struct device *dev, - struct device_attribute *dev_attr, - const char *buf, size_t count) -{ - return aux_store(dev, dev_attr, buf, count, 1); -} - -/* BIOS can enable/disable the thermal user application in dabney platform */ -#define BIOS_ENABLED "\\_TZ.GSTS" -static ssize_t bios_enabled_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - unsigned long long bios_enabled; - - status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); - if (ACPI_FAILURE(status)) - return -ENODEV; - - return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); -} - -static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show, - void *store, struct device *dev, - acpi_handle handle) -{ - struct intel_menlow_attribute *attr; - int result; - - attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL); - if (!attr) - return -ENOMEM; - - sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */ - attr->attr.attr.name = name; - attr->attr.attr.mode = mode; - attr->attr.show = show; - attr->attr.store = store; - attr->device = dev; - attr->handle = handle; - - result = device_create_file(dev, &attr->attr); - if (result) { - kfree(attr); - return result; - } - - mutex_lock(&intel_menlow_attr_lock); - list_add_tail(&attr->node, &intel_menlow_attr_list); - mutex_unlock(&intel_menlow_attr_lock); - - return 0; -} - -static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, - void *context, void **rv) -{ - acpi_status status; - acpi_handle dummy; - struct thermal_zone_device *thermal; - int result; - - result = acpi_bus_get_private_data(handle, (void **)&thermal); - if (result) - return 0; - - /* _TZ must have the AUX0/1 methods */ - status = acpi_get_handle(handle, GET_AUX0, &dummy); - if (ACPI_FAILURE(status)) - return (status == AE_NOT_FOUND) ? AE_OK : status; - - status = acpi_get_handle(handle, SET_AUX0, &dummy); - if (ACPI_FAILURE(status)) - return (status == AE_NOT_FOUND) ? AE_OK : status; - - result = intel_menlow_add_one_attribute("aux0", 0644, - aux0_show, aux0_store, - &thermal->device, handle); - if (result) - return AE_ERROR; - - status = acpi_get_handle(handle, GET_AUX1, &dummy); - if (ACPI_FAILURE(status)) - goto aux1_not_found; - - status = acpi_get_handle(handle, SET_AUX1, &dummy); - if (ACPI_FAILURE(status)) - goto aux1_not_found; - - result = intel_menlow_add_one_attribute("aux1", 0644, - aux1_show, aux1_store, - &thermal->device, handle); - if (result) { - intel_menlow_unregister_sensor(); - return AE_ERROR; - } - - /* - * create the "dabney_enabled" attribute which means the user app - * should be loaded or not - */ - - result = intel_menlow_add_one_attribute("bios_enabled", 0444, - bios_enabled_show, NULL, - &thermal->device, handle); - if (result) { - intel_menlow_unregister_sensor(); - return AE_ERROR; - } - - return AE_OK; - - aux1_not_found: - if (status == AE_NOT_FOUND) - return AE_OK; - - intel_menlow_unregister_sensor(); - return status; -} - -static void intel_menlow_unregister_sensor(void) -{ - struct intel_menlow_attribute *pos, *next; - - mutex_lock(&intel_menlow_attr_lock); - list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) { - list_del(&pos->node); - device_remove_file(pos->device, &pos->attr); - kfree(pos); - } - mutex_unlock(&intel_menlow_attr_lock); - - return; -} - -static int __init intel_menlow_module_init(void) -{ - int result = -ENODEV; - acpi_status status; - unsigned long long enable; - - if (acpi_disabled) - return result; - - /* Looking for the \_TZ.GSTS method */ - status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable); - if (ACPI_FAILURE(status) || !enable) - return -ENODEV; - - /* Looking for ACPI device MEM0 with hardware id INT0002 */ - result = acpi_bus_register_driver(&intel_menlow_memory_driver); - if (result) - return result; - - /* Looking for sensors in each ACPI thermal zone */ - status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, - intel_menlow_register_sensor, NULL, NULL, NULL); - if (ACPI_FAILURE(status)) { - acpi_bus_unregister_driver(&intel_menlow_memory_driver); - return -ENODEV; - } - - return 0; -} - -static void __exit intel_menlow_module_exit(void) -{ - acpi_bus_unregister_driver(&intel_menlow_memory_driver); - intel_menlow_unregister_sensor(); -} - -module_init(intel_menlow_module_init); -module_exit(intel_menlow_module_exit); diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig index e4299ca3423c..c83ea5d04a1d 100644 --- a/drivers/thermal/intel/Kconfig +++ b/drivers/thermal/intel/Kconfig @@ -90,3 +90,12 @@ config INTEL_TCC_COOLING Note that, on different platforms, the behavior might be different on how fast the setting takes effect, and how much the CPU frequency is reduced. + +config INTEL_MENLOW + tristate "Thermal Management driver for Intel menlow platform" + depends on ACPI_THERMAL + help + ACPI thermal management enhancement driver on + Intel Menlow platform. + + If unsure, say N. diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile index 5ff2afa388f7..960b56268b4a 100644 --- a/drivers/thermal/intel/Makefile +++ b/drivers/thermal/intel/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_INTEL_TCC_COOLING) += intel_tcc_cooling.o obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o +obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o diff --git a/drivers/thermal/intel/intel_menlow.c b/drivers/thermal/intel/intel_menlow.c new file mode 100644 index 000000000000..101d7e791a13 --- /dev/null +++ b/drivers/thermal/intel/intel_menlow.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel menlow Driver for thermal management extension + * + * Copyright (C) 2008 Intel Corp + * Copyright (C) 2008 Sujith Thomas + * Copyright (C) 2008 Zhang Rui + * + * This driver creates the sys I/F for programming the sensors. + * It also implements the driver for intel menlow memory controller (hardware + * id is INT0002) which makes use of the platform specific ACPI methods + * to get/set bandwidth. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Thomas Sujith"); +MODULE_AUTHOR("Zhang Rui"); +MODULE_DESCRIPTION("Intel Menlow platform specific driver"); +MODULE_LICENSE("GPL v2"); + +/* + * Memory controller device control + */ + +#define MEMORY_GET_BANDWIDTH "GTHS" +#define MEMORY_SET_BANDWIDTH "STHS" +#define MEMORY_ARG_CUR_BANDWIDTH 1 +#define MEMORY_ARG_MAX_BANDWIDTH 0 + +static void intel_menlow_unregister_sensor(void); + +/* + * GTHS returning 'n' would mean that [0,n-1] states are supported + * In that case max_cstate would be n-1 + * GTHS returning '0' would mean that no bandwidth control states are supported + */ +static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev, + unsigned long *max_state) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + unsigned long long value; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status = AE_OK; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH; + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, + &arg_list, &value); + if (ACPI_FAILURE(status)) + return -EFAULT; + + if (!value) + return -EINVAL; + + *max_state = value - 1; + return 0; +} + +static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, + unsigned long *value) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + unsigned long long result; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status = AE_OK; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH; + status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH, + &arg_list, &result); + if (ACPI_FAILURE(status)) + return -EFAULT; + + *value = result; + return 0; +} + +static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct acpi_device *device = cdev->devdata; + acpi_handle handle = device->handle; + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status; + unsigned long long temp; + unsigned long max_state; + + if (memory_get_max_bandwidth(cdev, &max_state)) + return -EFAULT; + + if (state > max_state) + return -EINVAL; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = state; + + status = + acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, + &temp); + + pr_info("Bandwidth value was %ld: status is %d\n", state, status); + if (ACPI_FAILURE(status)) + return -EFAULT; + + return 0; +} + +static const struct thermal_cooling_device_ops memory_cooling_ops = { + .get_max_state = memory_get_max_bandwidth, + .get_cur_state = memory_get_cur_bandwidth, + .set_cur_state = memory_set_cur_bandwidth, +}; + +/* + * Memory Device Management + */ +static int intel_menlow_memory_add(struct acpi_device *device) +{ + int result = -ENODEV; + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; + + if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH)) + goto end; + + if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH)) + goto end; + + cdev = thermal_cooling_device_register("Memory controller", device, + &memory_cooling_ops); + if (IS_ERR(cdev)) { + result = PTR_ERR(cdev); + goto end; + } + + device->driver_data = cdev; + result = sysfs_create_link(&device->dev.kobj, + &cdev->device.kobj, "thermal_cooling"); + if (result) + goto unregister; + + result = sysfs_create_link(&cdev->device.kobj, + &device->dev.kobj, "device"); + if (result) { + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); + goto unregister; + } + + end: + return result; + + unregister: + thermal_cooling_device_unregister(cdev); + return result; + +} + +static int intel_menlow_memory_remove(struct acpi_device *device) +{ + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; + + cdev = acpi_driver_data(device); + if (!cdev) + return -EINVAL; + + sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); + sysfs_remove_link(&cdev->device.kobj, "device"); + thermal_cooling_device_unregister(cdev); + + return 0; +} + +static const struct acpi_device_id intel_menlow_memory_ids[] = { + {"INT0002", 0}, + {"", 0}, +}; + +static struct acpi_driver intel_menlow_memory_driver = { + .name = "intel_menlow_thermal_control", + .ids = intel_menlow_memory_ids, + .ops = { + .add = intel_menlow_memory_add, + .remove = intel_menlow_memory_remove, + }, +}; + +/* + * Sensor control on menlow platform + */ + +#define THERMAL_AUX0 0 +#define THERMAL_AUX1 1 +#define GET_AUX0 "GAX0" +#define GET_AUX1 "GAX1" +#define SET_AUX0 "SAX0" +#define SET_AUX1 "SAX1" + +struct intel_menlow_attribute { + struct device_attribute attr; + struct device *device; + acpi_handle handle; + struct list_head node; +}; + +static LIST_HEAD(intel_menlow_attr_list); +static DEFINE_MUTEX(intel_menlow_attr_lock); + +/* + * sensor_get_auxtrip - get the current auxtrip value from sensor + * @name: Thermalzone name + * @auxtype : AUX0/AUX1 + * @buf: syfs buffer + */ +static int sensor_get_auxtrip(acpi_handle handle, int index, + unsigned long long *value) +{ + acpi_status status; + + if ((index != 0 && index != 1) || !value) + return -EINVAL; + + status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, + NULL, value); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * sensor_set_auxtrip - set the new auxtrip value to sensor + * @name: Thermalzone name + * @auxtype : AUX0/AUX1 + * @buf: syfs buffer + */ +static int sensor_set_auxtrip(acpi_handle handle, int index, int value) +{ + acpi_status status; + union acpi_object arg = { + ACPI_TYPE_INTEGER + }; + struct acpi_object_list args = { + 1, &arg + }; + unsigned long long temp; + + if (index != 0 && index != 1) + return -EINVAL; + + status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, + NULL, &temp); + if (ACPI_FAILURE(status)) + return -EIO; + if ((index && value < temp) || (!index && value > temp)) + return -EINVAL; + + arg.integer.value = value; + status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, + &args, &temp); + if (ACPI_FAILURE(status)) + return -EIO; + + /* do we need to check the return value of SAX0/SAX1 ? */ + + return 0; +} + +#define to_intel_menlow_attr(_attr) \ + container_of(_attr, struct intel_menlow_attribute, attr) + +static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr, + char *buf, int idx) +{ + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); + unsigned long long value; + int result; + + result = sensor_get_auxtrip(attr->handle, idx, &value); + if (result) + return result; + + return sprintf(buf, "%lu", deci_kelvin_to_celsius(value)); +} + +static ssize_t aux0_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return aux_show(dev, dev_attr, buf, 0); +} + +static ssize_t aux1_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return aux_show(dev, dev_attr, buf, 1); +} + +static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr, + const char *buf, size_t count, int idx) +{ + struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); + int value; + int result; + + /*Sanity check; should be a positive integer */ + if (!sscanf(buf, "%d", &value)) + return -EINVAL; + + if (value < 0) + return -EINVAL; + + result = sensor_set_auxtrip(attr->handle, idx, + celsius_to_deci_kelvin(value)); + return result ? result : count; +} + +static ssize_t aux0_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + return aux_store(dev, dev_attr, buf, count, 0); +} + +static ssize_t aux1_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + return aux_store(dev, dev_attr, buf, count, 1); +} + +/* BIOS can enable/disable the thermal user application in dabney platform */ +#define BIOS_ENABLED "\\_TZ.GSTS" +static ssize_t bios_enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + unsigned long long bios_enabled; + + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled"); +} + +static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show, + void *store, struct device *dev, + acpi_handle handle) +{ + struct intel_menlow_attribute *attr; + int result; + + attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL); + if (!attr) + return -ENOMEM; + + sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */ + attr->attr.attr.name = name; + attr->attr.attr.mode = mode; + attr->attr.show = show; + attr->attr.store = store; + attr->device = dev; + attr->handle = handle; + + result = device_create_file(dev, &attr->attr); + if (result) { + kfree(attr); + return result; + } + + mutex_lock(&intel_menlow_attr_lock); + list_add_tail(&attr->node, &intel_menlow_attr_list); + mutex_unlock(&intel_menlow_attr_lock); + + return 0; +} + +static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + acpi_status status; + acpi_handle dummy; + struct thermal_zone_device *thermal; + int result; + + result = acpi_bus_get_private_data(handle, (void **)&thermal); + if (result) + return 0; + + /* _TZ must have the AUX0/1 methods */ + status = acpi_get_handle(handle, GET_AUX0, &dummy); + if (ACPI_FAILURE(status)) + return (status == AE_NOT_FOUND) ? AE_OK : status; + + status = acpi_get_handle(handle, SET_AUX0, &dummy); + if (ACPI_FAILURE(status)) + return (status == AE_NOT_FOUND) ? AE_OK : status; + + result = intel_menlow_add_one_attribute("aux0", 0644, + aux0_show, aux0_store, + &thermal->device, handle); + if (result) + return AE_ERROR; + + status = acpi_get_handle(handle, GET_AUX1, &dummy); + if (ACPI_FAILURE(status)) + goto aux1_not_found; + + status = acpi_get_handle(handle, SET_AUX1, &dummy); + if (ACPI_FAILURE(status)) + goto aux1_not_found; + + result = intel_menlow_add_one_attribute("aux1", 0644, + aux1_show, aux1_store, + &thermal->device, handle); + if (result) { + intel_menlow_unregister_sensor(); + return AE_ERROR; + } + + /* + * create the "dabney_enabled" attribute which means the user app + * should be loaded or not + */ + + result = intel_menlow_add_one_attribute("bios_enabled", 0444, + bios_enabled_show, NULL, + &thermal->device, handle); + if (result) { + intel_menlow_unregister_sensor(); + return AE_ERROR; + } + + return AE_OK; + + aux1_not_found: + if (status == AE_NOT_FOUND) + return AE_OK; + + intel_menlow_unregister_sensor(); + return status; +} + +static void intel_menlow_unregister_sensor(void) +{ + struct intel_menlow_attribute *pos, *next; + + mutex_lock(&intel_menlow_attr_lock); + list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) { + list_del(&pos->node); + device_remove_file(pos->device, &pos->attr); + kfree(pos); + } + mutex_unlock(&intel_menlow_attr_lock); + + return; +} + +static int __init intel_menlow_module_init(void) +{ + int result = -ENODEV; + acpi_status status; + unsigned long long enable; + + if (acpi_disabled) + return result; + + /* Looking for the \_TZ.GSTS method */ + status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable); + if (ACPI_FAILURE(status) || !enable) + return -ENODEV; + + /* Looking for ACPI device MEM0 with hardware id INT0002 */ + result = acpi_bus_register_driver(&intel_menlow_memory_driver); + if (result) + return result; + + /* Looking for sensors in each ACPI thermal zone */ + status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, + intel_menlow_register_sensor, NULL, NULL, NULL); + if (ACPI_FAILURE(status)) { + acpi_bus_unregister_driver(&intel_menlow_memory_driver); + return -ENODEV; + } + + return 0; +} + +static void __exit intel_menlow_module_exit(void) +{ + acpi_bus_unregister_driver(&intel_menlow_memory_driver); + intel_menlow_unregister_sensor(); +} + +module_init(intel_menlow_module_init); +module_exit(intel_menlow_module_exit); -- cgit v1.2.3 From 45b6f75eab6aabf9d88933830f41f532d39f38d2 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 13 Aug 2021 18:47:28 -0700 Subject: platform/x86: intel_pmc_core: Prevent possibile overflow Substate priority levels are encoded in 4 bits in the LPM_PRI register. This value was used as an index to an array whose element size was less than 16, leading to the possibility of overflow should we read a larger than expected priority. In addition to the overflow, bad values could lead to incorrect state reporting. So rework the priority code to prevent the overflow and perform some validation of the register. Use the priority register values if they give an ordering of unique numbers between 0 and the maximum number of states. Otherwise, use a default ordering instead. Reported-by: Evgeny Novikov Signed-off-by: David E. Box Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210814014728.520856-1-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_pmc_core.c | 65 +++++++++++++++++++++++++++-------- drivers/platform/x86/intel_pmc_core.h | 2 ++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index b0e486a6bdfb..ae410a358ffe 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -1449,9 +1449,42 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc); -static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) +static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order) { - u8 lpm_priority[LPM_MAX_NUM_MODES]; + 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; @@ -1462,24 +1495,28 @@ static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset); pmcdev->num_lpm_modes = hweight32(lpm_en); - /* Each byte contains information for 2 modes (7:4 and 3:0) */ - for (mode = 0; mode < LPM_MAX_NUM_MODES; mode += 2) { - u8 priority = pmc_core_reg_read_byte(pmcdev, - pmcdev->map->lpm_priority_offset + (mode / 2)); - int pri0 = GENMASK(3, 0) & priority; - int pri1 = (GENMASK(7, 4) & priority) >> 4; + /* Read 32 bit LPM_PRI register */ + lpm_pri = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_priority_offset); - lpm_priority[pri0] = mode; - lpm_priority[pri1] = mode + 1; - } /* - * Loop though all modes from lowest to highest priority, + * 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 = lpm_priority[p]; + int mode = pri_order[p]; if (!(BIT(mode) & lpm_en)) continue; @@ -1675,7 +1712,7 @@ static int pmc_core_probe(struct platform_device *pdev) mutex_init(&pmcdev->lock); pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev); - pmc_core_get_low_power_modes(pmcdev); + pmc_core_get_low_power_modes(pdev); pmc_core_do_dmi_quirks(pmcdev); if (pmcdev->map == &tgl_reg_map) diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h index e8dae9c6c45f..b9bf3d3d6f7a 100644 --- a/drivers/platform/x86/intel_pmc_core.h +++ b/drivers/platform/x86/intel_pmc_core.h @@ -188,6 +188,8 @@ enum ppfear_regs { #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) -- cgit v1.2.3 From f5bc0157be9baf1e2f12fb53f1e392b955e1c57f Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Mon, 16 Aug 2021 20:15:01 -0400 Subject: platform/x86: think-lmi: add debug_cmd Many Lenovo BIOS's support the ability to send a debug command which is useful for debugging and testing unreleased or early features. Adding support for this feature as a module parameter. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20210817001501.293501-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-class-firmware-attributes | 11 +++ drivers/platform/x86/think-lmi.c | 80 ++++++++++++++++++++++ drivers/platform/x86/think-lmi.h | 1 + 3 files changed, 92 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 0b43997b76e3..90fdf935aa5e 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -272,3 +272,14 @@ Description: Note that any changes to this attribute requires a reboot for changes to take effect. + +What: /sys/class/firmware-attributes/*/attributes/debug_cmd +Date: July 2021 +KernelVersion: 5.14 +Contact: Mark Pearson +Description: + This write only attribute can be used to send debug commands to the BIOS. + This should only be used when recommended by the BIOS vendor. Vendors may + use it to enable extra debug attributes or BIOS features for testing purposes. + + Note that any changes to this attribute requires a reboot for changes to take effect. diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 6cfed4427fb0..9472aae72df2 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -20,6 +20,10 @@ #include "firmware_attributes_class.h" #include "think-lmi.h" +static bool debug_support; +module_param(debug_support, bool, 0444); +MODULE_PARM_DESC(debug_support, "Enable debug command support"); + /* * Name: * Lenovo_BiosSetting @@ -116,6 +120,14 @@ */ #define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" +/* + * Name: + * Lenovo_DebugCmdGUID + * Description + * Debug entry GUID method for entering debug commands to the BIOS + */ +#define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1" + #define TLMI_POP_PWD (1 << 0) #define TLMI_PAP_PWD (1 << 1) #define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) @@ -660,6 +672,64 @@ static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute * static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); +/* ---- Debug interface--------------------------------------------------------- */ +static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *set_str = NULL, *new_setting = NULL; + char *auth_str = NULL; + char *p; + int ret; + + if (!tlmi_priv.can_debug_cmd) + return -EOPNOTSUPP; + + new_setting = kstrdup(buf, GFP_KERNEL); + if (!new_setting) + return -ENOMEM; + + /* Strip out CR if one is present */ + p = strchrnul(new_setting, '\n'); + *p = '\0'; + + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", + tlmi_priv.pwd_admin->password, + encoding_options[tlmi_priv.pwd_admin->encoding], + tlmi_priv.pwd_admin->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + } + + if (auth_str) + set_str = kasprintf(GFP_KERNEL, "%s,%s", new_setting, auth_str); + else + set_str = kasprintf(GFP_KERNEL, "%s;", new_setting); + if (!set_str) { + ret = -ENOMEM; + goto out; + } + + ret = tlmi_simple_call(LENOVO_DEBUG_CMD_GUID, set_str); + if (ret) + goto out; + + if (!ret && !tlmi_priv.pending_changes) { + tlmi_priv.pending_changes = true; + /* let userland know it may need to check reboot pending again */ + kobject_uevent(&tlmi_priv.class_dev->kobj, KOBJ_CHANGE); + } +out: + kfree(auth_str); + kfree(set_str); + kfree(new_setting); + return ret ?: count; +} + +static struct kobj_attribute debug_cmd = __ATTR_WO(debug_cmd); + /* ---- Initialisation --------------------------------------------------------- */ static void tlmi_release_attr(void) { @@ -673,6 +743,8 @@ static void tlmi_release_attr(void) } } sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); + if (tlmi_priv.can_debug_cmd && debug_support) + sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); kset_unregister(tlmi_priv.attribute_kset); /* Authentication structures */ @@ -737,6 +809,11 @@ static int tlmi_sysfs_init(void) if (ret) goto fail_create_attr; + if (tlmi_priv.can_debug_cmd && debug_support) { + ret = sysfs_create_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); + if (ret) + goto fail_create_attr; + } /* Create authentication entries */ tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, &tlmi_priv.class_dev->kobj); @@ -793,6 +870,9 @@ static int tlmi_analyze(void) if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID)) tlmi_priv.can_get_password_settings = true; + if (wmi_has_guid(LENOVO_DEBUG_CMD_GUID)) + tlmi_priv.can_debug_cmd = true; + /* * Try to find the number of valid settings of this machine * and use it to create sysfs attributes. diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h index eb598846628a..f8e26823075f 100644 --- a/drivers/platform/x86/think-lmi.h +++ b/drivers/platform/x86/think-lmi.h @@ -61,6 +61,7 @@ struct think_lmi { bool can_set_bios_password; bool can_get_password_settings; bool pending_changes; + bool can_debug_cmd; struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; struct device *class_dev; -- cgit v1.2.3 From f709d0bbad1975dc037287b6fa49781cfce791e5 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 17 Aug 2021 17:46:28 +0200 Subject: platform/x86: gigabyte-wmi: add support for X570 GAMING X MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as working here: https://github.com/t-8ch/linux-gigabyte-wmi-driver/issues/1#issuecomment-900263115 Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20210817154628.84992-1-linux@weissschuh.net Signed-off-by: Hans de Goede --- drivers/platform/x86/gigabyte-wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c index fbb224a82e34..9e8cfac403d3 100644 --- a/drivers/platform/x86/gigabyte-wmi.c +++ b/drivers/platform/x86/gigabyte-wmi.c @@ -147,6 +147,7 @@ static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = { DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550M DS3H"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("Z390 I AORUS PRO WIFI-CF"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 AORUS ELITE"), + DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 GAMING X"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 I AORUS PRO WIFI"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 UD"), { } -- cgit v1.2.3 From 30f64e2066ab1b51139307eb33dc217838bd19bc Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 18 Aug 2021 18:44:35 +0200 Subject: platform/x86: gigabyte-wmi: add support for B450M S2H V2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported as working here: https://github.com/t-8ch/linux-gigabyte-wmi-driver/issues/1#issuecomment-901207693 Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20210818164435.99821-1-linux@weissschuh.net Signed-off-by: Hans de Goede --- drivers/platform/x86/gigabyte-wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c index 9e8cfac403d3..7f3a03f937f6 100644 --- a/drivers/platform/x86/gigabyte-wmi.c +++ b/drivers/platform/x86/gigabyte-wmi.c @@ -140,6 +140,7 @@ static u8 gigabyte_wmi_detect_sensor_usability(struct wmi_device *wdev) }} static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = { + DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B450M S2H V2"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 AORUS ELITE"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 AORUS ELITE V2"), DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 GAMING X V2"), -- cgit v1.2.3 From 3ae86d2d4704796ee658a34245cb86e68c40c5d7 Mon Sep 17 00:00:00 2001 From: Meng Dong Date: Wed, 18 Aug 2021 01:12:03 +0800 Subject: platform/x86: ideapad-laptop: Fix Legion 5 Fn lock LED This patch fixes the bug 212671. Althrough the Fn lock (Fn + Esc) works on Legion 5 (R7000P), its LED light does not change with the state. This modification sets the Fn lock state to its current value on receiving the wmi event 8FC0DE0C-B4E4-43FD-B0F3-8871711C1294 to update the LED state. Signed-off-by: Meng Dong Link: https://lore.kernel.org/r/20210817171203.12855-1-whenov@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 784326bd72f0..e7a1299e3776 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -41,6 +41,7 @@ static const char *const ideapad_wmi_fnesc_events[] = { "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ + "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", /* Legion 5 */ }; #endif @@ -1459,11 +1460,19 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) static void ideapad_wmi_notify(u32 value, void *context) { struct ideapad_private *priv = context; + unsigned long result; switch (value) { case 128: ideapad_input_report(priv, value); break; + case 208: + if (!eval_hals(priv->adev->handle, &result)) { + bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); + + exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); + } + break; default: dev_info(&priv->platform_device->dev, "Unknown WMI event: %u\n", value); -- cgit v1.2.3 From ef195e8a7f43924b9979b2cd81ac7fa54f24bb3c Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 17 Aug 2021 15:40:17 -0700 Subject: platform/x86: intel_pmt_telemetry: Ignore zero sized entries Some devices may expose non-functioning entries that are reserved for future use. These entries have zero size. Ignore them during probe. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20210817224018.1013192-5-david.e.box@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmt/telemetry.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index a58843360fbf..38d52651c572 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -61,6 +61,14 @@ static int pmt_telem_header_decode(struct intel_pmt_entry *entry, /* 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; } -- cgit v1.2.3 From dcfbd31ef4bcf6ceb373911faa4a5299e0547702 Mon Sep 17 00:00:00 2001 From: Shravan S Date: Sat, 24 Jul 2021 02:44:52 +0530 Subject: platform/x86: BIOS SAR driver for Intel M.2 Modem Dynamic BIOS SAR driver exposing dynamic SAR information from BIOS The Dynamic SAR (Specific Absorption Rate) driver uses ACPI DSM (Device Specific Method) to communicate with BIOS and retrieve dynamic SAR information and change notifications. The driver uses sysfs to expose this data to userspace via read and notify. Sysfs interface is documented in detail under: Documentation/ABI/testing/sysfs-driver-intc_sar Signed-off-by: Shravan S Link: https://lore.kernel.org/r/20210723211452.27995-2-s.shravan@intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/ABI/testing/sysfs-driver-intc_sar | 54 ++++ MAINTAINERS | 7 + drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/int1092/Kconfig | 14 ++ drivers/platform/x86/intel/int1092/Makefile | 1 + drivers/platform/x86/intel/int1092/intel_sar.c | 316 ++++++++++++++++++++++++ drivers/platform/x86/intel/int1092/intel_sar.h | 86 +++++++ 8 files changed, 480 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-intc_sar create mode 100644 drivers/platform/x86/intel/int1092/Kconfig create mode 100644 drivers/platform/x86/intel/int1092/Makefile create mode 100644 drivers/platform/x86/intel/int1092/intel_sar.c create mode 100644 drivers/platform/x86/intel/int1092/intel_sar.h diff --git a/Documentation/ABI/testing/sysfs-driver-intc_sar b/Documentation/ABI/testing/sysfs-driver-intc_sar new file mode 100644 index 000000000000..ec334b0e5ed9 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intc_sar @@ -0,0 +1,54 @@ +What: /sys/bus/platform/devices/INTC1092:00/intc_reg +Date: August 2021 +KernelVersion: 5.15 +Contact: Shravan S , + An Sudhakar +Description: + Specific Absorption Rate (SAR) regulatory mode is typically + derived based on information like mcc (Mobile Country Code) and + mnc (Mobile Network Code) that is available for the currently + attached LTE network. A userspace application is required to set + the current SAR regulatory mode on the Dynamic SAR driver using + this sysfs node. Such an application can also read back using + this sysfs node, the currently configured regulatory mode value + from the Dynamic SAR driver. + + Acceptable regulatory modes are: + == ==== + 0 FCC + 1 CE + 2 ISED + == ==== + + - The regulatory mode value has one of the above values. + - The default regulatory mode used in the driver is 0. + +What: /sys/bus/platform/devices/INTC1092:00/intc_data +Date: August 2021 +KernelVersion: 5.15 +Contact: Shravan S , + An Sudhakar +Description: + This sysfs entry is used to retrieve Dynamic SAR information + emitted/maintained by a BIOS that supports Dynamic SAR. + + The retrieved information is in the order given below: + - device_mode + - bandtable_index + - antennatable_index + - sartable_index + + The above information is sent as integer values separated + by a single space. This information can then be pushed to a + WWAN modem that uses this to control the transmit signal + level using the Band/Antenna/SAR table index information. + These parameters are derived/decided by aggregating + device-mode like laptop/tablet/clamshell etc. and the + proximity-sensor data available to the embedded controller on + given host. The regulatory mode configured on Dynamic SAR + driver also influences these values. + + The userspace applications can poll for changes to this file + using POLLPRI event on file-descriptor (fd) obtained by opening + this sysfs entry. Application can then read this information from + the sysfs node and consume the given information. diff --git a/MAINTAINERS b/MAINTAINERS index 279af7e9a281..b731988a4452 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9220,6 +9220,13 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/intel_atomisp2_led.c +INTEL BIOS SAR INT1092 DRIVER +M: Shravan S +M: Intel Corporation +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/intel/int1092/ + INTEL BROXTON PMC DRIVER M: Mika Westerberg M: Zha Qipeng diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 6eec084d9bf9..4dd1fd4450ad 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -16,6 +16,7 @@ menuconfig X86_PLATFORM_DRIVERS_INTEL if X86_PLATFORM_DRIVERS_INTEL +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/pmt/Kconfig" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index ca0ec2c85b05..dc6baf420808 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -4,6 +4,7 @@ # Intel x86 Platform-Specific Drivers # +obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ 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 +#include +#include +#include +#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 "); 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 */ -- cgit v1.2.3 From 8983bfd58d61ab29ab4114089d1f42e1ee6103a8 Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Wed, 18 Aug 2021 12:25:14 +0300 Subject: platform/x86: lg-laptop: Support for battery charge limit on newer models Add support for the difference between various models: - Use dmi to detect laptop model. - 2019 and newer models use _wmbb method to set battery charge limit. Signed-off-by: Matan Ziv-Av Link: https://lore.kernel.org/r/bd6922a412e50c2dcfb7ce24fc8687f577181d65.1629291912.git.matan@svgalib.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/lg-laptop.c | 75 +++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 20145b539335..c78efeee1c19 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -8,6 +8,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -69,6 +70,8 @@ static u32 inited; #define INIT_INPUT_ACPI 0x04 #define INIT_SPARSE_KEYMAP 0x80 +static int battery_limit_use_wmbb; + static const struct key_entry wmi_keymap[] = { {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ {KE_KEY, 0x74, {KEY_F13} }, /* Touchpad toggle (F5) */ @@ -461,7 +464,10 @@ static ssize_t battery_care_limit_store(struct device *dev, if (value == 100 || value == 80) { union acpi_object *r; - r = lg_wmab(WM_BATT_LIMIT, WM_SET, value); + if (battery_limit_use_wmbb) + r = lg_wmbb(WMBB_BATT_LIMIT, WM_SET, value); + else + r = lg_wmab(WM_BATT_LIMIT, WM_SET, value); if (!r) return -EIO; @@ -479,16 +485,29 @@ static ssize_t battery_care_limit_show(struct device *dev, unsigned int status; union acpi_object *r; - r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0); - if (!r) - return -EIO; + if (battery_limit_use_wmbb) { + r = lg_wmbb(WMBB_BATT_LIMIT, WM_GET, 0); + if (!r) + return -EIO; - if (r->type != ACPI_TYPE_INTEGER) { - kfree(r); - return -EIO; - } + if (r->type != ACPI_TYPE_BUFFER) { + kfree(r); + return -EIO; + } - status = r->integer.value; + status = r->buffer.pointer[0x10]; + } else { + r = lg_wmab(WM_BATT_LIMIT, WM_GET, 0); + if (!r) + return -EIO; + + if (r->type != ACPI_TYPE_INTEGER) { + kfree(r); + return -EIO; + } + + status = r->integer.value; + } kfree(r); if (status != 80 && status != 100) status = 0; @@ -602,6 +621,8 @@ static struct platform_driver pf_driver = { static int acpi_add(struct acpi_device *device) { int ret; + const char *product; + int year = 2017; if (pf_device) return 0; @@ -619,6 +640,42 @@ static int acpi_add(struct acpi_device *device) pr_err("unable to register platform device\n"); goto out_platform_registered; } + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (strlen(product) > 4) + switch (product[4]) { + case '5': + case '6': + year = 2016; + break; + case '7': + year = 2017; + break; + case '8': + year = 2018; + break; + case '9': + year = 2019; + break; + case '0': + if (strlen(product) > 5) + switch (product[5]) { + case 'N': + year = 2020; + break; + case 'P': + year = 2021; + break; + default: + year = 2022; + } + break; + default: + year = 2019; + } + pr_info("product: %s year: %d\n", product, year); + + if (year >= 2019) + battery_limit_use_wmbb = 1; ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); if (ret) -- cgit v1.2.3 From 85973bf4c1b19cc1c6e801d492645bd63275573e Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Wed, 18 Aug 2021 12:34:50 +0300 Subject: platform/x86: lg-laptop: Use correct event for touchpad toggle FN-key Send F21 which is the standard for this key, instead of F13. Signed-off-by: Matan Ziv-Av Link: https://lore.kernel.org/r/b847895c1f170e2e59df5757a4d603d28149f648.1629291912.git.matan@svgalib.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/admin-guide/laptops/lg-laptop.rst | 2 +- drivers/platform/x86/lg-laptop.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/laptops/lg-laptop.rst b/Documentation/admin-guide/laptops/lg-laptop.rst index ce9b14671cb9..13d7ec427e76 100644 --- a/Documentation/admin-guide/laptops/lg-laptop.rst +++ b/Documentation/admin-guide/laptops/lg-laptop.rst @@ -13,7 +13,7 @@ Hotkeys The following FN keys are ignored by the kernel without this driver: - FN-F1 (LG control panel) - Generates F15 -- FN-F5 (Touchpad toggle) - Generates F13 +- FN-F5 (Touchpad toggle) - Generates F21 - FN-F6 (Airplane mode) - Generates RFKILL - FN-F8 (Keyboard backlight) - Generates F16. This key also changes keyboard backlight mode. diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index c78efeee1c19..12b497a11c6f 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -74,7 +74,7 @@ static int battery_limit_use_wmbb; static const struct key_entry wmi_keymap[] = { {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ - {KE_KEY, 0x74, {KEY_F13} }, /* Touchpad toggle (F5) */ + {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing * this key both sends an event and -- cgit v1.2.3 From ae26278829a80ad0e60ff004de71e9276cee5dc0 Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Wed, 18 Aug 2021 15:47:31 +0300 Subject: platform/x86: lg-laptop: Use correct event for keyboard backlight FN-key Use led_classdev_notify_brightness_hw_changed() instead of F16 key. Signed-off-by: Matan Ziv-Av Link: https://lore.kernel.org/r/2196990f167efe6a42d51fb85f4db4cdf4d9e80e.1629291912.git.matan@svgalib.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/admin-guide/laptops/lg-laptop.rst | 2 -- drivers/platform/x86/lg-laptop.c | 30 ++++++++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/laptops/lg-laptop.rst b/Documentation/admin-guide/laptops/lg-laptop.rst index 13d7ec427e76..6fbe165dcd27 100644 --- a/Documentation/admin-guide/laptops/lg-laptop.rst +++ b/Documentation/admin-guide/laptops/lg-laptop.rst @@ -15,8 +15,6 @@ The following FN keys are ignored by the kernel without this driver: - FN-F1 (LG control panel) - Generates F15 - FN-F5 (Touchpad toggle) - Generates F21 - FN-F6 (Airplane mode) - Generates RFKILL -- FN-F8 (Keyboard backlight) - Generates F16. - This key also changes keyboard backlight mode. - FN-F9 (Reader mode) - Generates F14 The rest of the FN keys work without a need for a special driver. diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 12b497a11c6f..3e520d5bca07 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -17,11 +17,12 @@ #include #include -#define LED_DEVICE(_name, max) struct led_classdev _name = { \ +#define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \ .name = __stringify(_name), \ .max_brightness = max, \ .brightness_set = _name##_set, \ .brightness_get = _name##_get, \ + .flags = flag, \ } MODULE_AUTHOR("Matan Ziv-Av"); @@ -71,6 +72,8 @@ static u32 inited; #define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; +static struct led_classdev kbd_backlight; +static enum led_brightness get_kbd_backlight_level(void); static const struct key_entry wmi_keymap[] = { {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ @@ -217,10 +220,16 @@ static void wmi_notify(u32 value, void *context) int eventcode = obj->integer.value; struct key_entry *key; - key = - sparse_keymap_entry_from_scancode(wmi_input_dev, eventcode); - if (key && key->type == KE_KEY) - sparse_keymap_report_entry(wmi_input_dev, key, 1, true); + if (eventcode == 0x10000000) { + led_classdev_notify_brightness_hw_changed( + &kbd_backlight, get_kbd_backlight_level()); + } else { + key = sparse_keymap_entry_from_scancode( + wmi_input_dev, eventcode); + if (key && key->type == KE_KEY) + sparse_keymap_report_entry(wmi_input_dev, + key, 1, true); + } } pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, @@ -548,7 +557,7 @@ static enum led_brightness tpad_led_get(struct led_classdev *cdev) return ggov(GOV_TLED) > 0 ? LED_ON : LED_OFF; } -static LED_DEVICE(tpad_led, 1); +static LED_DEVICE(tpad_led, 1, 0); static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) @@ -565,7 +574,7 @@ static void kbd_backlight_set(struct led_classdev *cdev, kfree(r); } -static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +static enum led_brightness get_kbd_backlight_level(void) { union acpi_object *r; int val; @@ -596,7 +605,12 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) return val; } -static LED_DEVICE(kbd_backlight, 255); +static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +{ + return get_kbd_backlight_level(); +} + +static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); static void wmi_input_destroy(void) { -- cgit v1.2.3 From c63d44ae602419eda128cecd33d226f3d3f18df2 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Thu, 19 Aug 2021 07:07:31 +1200 Subject: asus-wmi: Add support for platform_profile Add initial support for platform_profile where the support is based on availability of ASUS_THROTTLE_THERMAL_POLICY. Because throttle_thermal_policy is used by platform_profile and is writeable separately to platform_profile any userspace changes to throttle_thermal_policy need to notify platform_profile. In future throttle_thermal_policy sysfs should be removed so that only one method controls the laptop power profile. Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210818190731.19170-2-luke@ljones.dev Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/asus-wmi.c | 130 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a87a4960256d..9868e26b37ed 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -281,6 +281,7 @@ config ASUS_WMI select INPUT_SPARSEKMAP select LEDS_CLASS select NEW_LEDS + select ACPI_PLATFORM_PROFILE help Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new Asus Notebooks). diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 90a6a0d00deb..cc5811844012 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,9 @@ struct asus_wmi { bool throttle_thermal_policy_available; u8 throttle_thermal_policy_mode; + struct platform_profile_handler platform_profile_handler; + bool platform_profile_support; + // The RSOC controls the maximum charging percentage. bool battery_rsoc_available; @@ -2103,12 +2107,23 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus) static int throttle_thermal_policy_switch_next(struct asus_wmi *asus) { u8 new_mode = asus->throttle_thermal_policy_mode + 1; + int err; if (new_mode > ASUS_THROTTLE_THERMAL_POLICY_SILENT) new_mode = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT; asus->throttle_thermal_policy_mode = new_mode; - return throttle_thermal_policy_write(asus); + err = throttle_thermal_policy_write(asus); + if (err) + return err; + + /* + * Ensure that platform_profile updates userspace with the change to ensure + * that platform_profile and throttle_thermal_policy_mode are in sync. + */ + platform_profile_notify(); + + return 0; } static ssize_t throttle_thermal_policy_show(struct device *dev, @@ -2124,9 +2139,10 @@ static ssize_t throttle_thermal_policy_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int result; - u8 new_mode; struct asus_wmi *asus = dev_get_drvdata(dev); + u8 new_mode; + int result; + int err; result = kstrtou8(buf, 10, &new_mode); if (result < 0) @@ -2136,7 +2152,15 @@ static ssize_t throttle_thermal_policy_store(struct device *dev, return -EINVAL; asus->throttle_thermal_policy_mode = new_mode; - throttle_thermal_policy_write(asus); + err = throttle_thermal_policy_write(asus); + if (err) + return err; + + /* + * Ensure that platform_profile updates userspace with the change to ensure + * that platform_profile and throttle_thermal_policy_mode are in sync. + */ + platform_profile_notify(); return count; } @@ -2144,6 +2168,94 @@ static ssize_t throttle_thermal_policy_store(struct device *dev, // Throttle thermal policy: 0 - default, 1 - overboost, 2 - silent static DEVICE_ATTR_RW(throttle_thermal_policy); +/* Platform profile ***********************************************************/ +static int platform_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + struct asus_wmi *asus; + int tp; + + asus = container_of(pprof, struct asus_wmi, platform_profile_handler); + + tp = asus->throttle_thermal_policy_mode; + + if (tp < 0) + return tp; + + switch (tp) { + case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case ASUS_THROTTLE_THERMAL_POLICY_SILENT: + *profile = PLATFORM_PROFILE_QUIET; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int platform_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + struct asus_wmi *asus; + int tp; + + asus = container_of(pprof, struct asus_wmi, platform_profile_handler); + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + tp = ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST; + break; + case PLATFORM_PROFILE_BALANCED: + tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT; + break; + case PLATFORM_PROFILE_QUIET: + tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT; + break; + default: + return -EOPNOTSUPP; + } + + asus->throttle_thermal_policy_mode = tp; + return throttle_thermal_policy_write(asus); +} + +static int platform_profile_setup(struct asus_wmi *asus) +{ + struct device *dev = &asus->platform_device->dev; + int err; + + /* + * Not an error if a component platform_profile relies on is unavailable + * so early return, skipping the setup of platform_profile. + */ + if (!asus->throttle_thermal_policy_available) + return 0; + + dev_info(dev, "Using throttle_thermal_policy for platform_profile support\n"); + + asus->platform_profile_handler.profile_get = platform_profile_get; + asus->platform_profile_handler.profile_set = platform_profile_set; + + set_bit(PLATFORM_PROFILE_QUIET, asus->platform_profile_handler.choices); + set_bit(PLATFORM_PROFILE_BALANCED, + asus->platform_profile_handler.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, + asus->platform_profile_handler.choices); + + err = platform_profile_register(&asus->platform_profile_handler); + if (err) + return err; + + asus->platform_profile_support = true; + return 0; +} + /* Backlight ******************************************************************/ static int read_backlight_power(struct asus_wmi *asus) @@ -2904,6 +3016,10 @@ static int asus_wmi_add(struct platform_device *pdev) else throttle_thermal_policy_set_default(asus); + err = platform_profile_setup(asus); + if (err) + goto fail_platform_profile_setup; + err = panel_od_check_present(asus); if (err) goto fail_panel_od; @@ -2993,6 +3109,9 @@ fail_input: asus_wmi_sysfs_exit(asus->platform_device); fail_sysfs: fail_throttle_thermal_policy: +fail_platform_profile_setup: + if (asus->platform_profile_support) + platform_profile_remove(); fail_fan_boost_mode: fail_egpu_enable: fail_dgpu_disable: @@ -3017,6 +3136,9 @@ static int asus_wmi_remove(struct platform_device *device) asus_fan_set_auto(asus); asus_wmi_battery_exit(asus); + if (asus->platform_profile_support) + platform_profile_remove(); + kfree(asus); return 0; } -- cgit v1.2.3 From b38d4ef1f0fdc23d8c0a8d2c349b61f2ae60c717 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Aug 2021 14:04:39 +0300 Subject: platform/x86: intel_scu_ipc: Fix doc of intel_scu_ipc_dev_command_with_size() The kernel doc validator complains: .../ipc.c:478: warning: expecting prototype for intel_scu_ipc_command_with_size(). Prototype was for intel_scu_ipc_dev_command_with_size() instead Fix the prototype name in the kernel documentation. Reported-by: kernel test robot Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-2-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_scu_ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index 9171a46a9e3f..bfa0cc20750d 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -457,7 +457,7 @@ int intel_scu_ipc_dev_simple_command(struct intel_scu_ipc_dev *scu, int cmd, EXPORT_SYMBOL(intel_scu_ipc_dev_simple_command); /** - * intel_scu_ipc_command_with_size() - Command with data + * intel_scu_ipc_dev_command_with_size() - Command with data * @scu: Optional SCU IPC instance * @cmd: Command * @sub: Sub type -- cgit v1.2.3 From 9ed10052b5c9615a45830d75206608b19b830b16 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:40 +0300 Subject: platform/x86: intel_bxtwc_tmu: Move to intel sub-directory Move Intel Broxton Whiskey Cove TMU driver to intel sub-directory to improve readability. While at it, spell BXT fully in the Kconfig and switch to select REGMAP. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-3-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 10 --- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 10 +++ drivers/platform/x86/intel/Makefile | 4 + drivers/platform/x86/intel/bxtwc_tmu.c | 147 +++++++++++++++++++++++++++++++++ drivers/platform/x86/intel_bxtwc_tmu.c | 147 --------------------------------- 6 files changed, 161 insertions(+), 158 deletions(-) create mode 100644 drivers/platform/x86/intel/bxtwc_tmu.c delete mode 100644 drivers/platform/x86/intel_bxtwc_tmu.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9868e26b37ed..084167d70f1b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1159,16 +1159,6 @@ config INTEL_UNCORE_FREQ_CONTROL To compile this driver as a module, choose M here: the module will be called intel-uncore-frequency. -config INTEL_BXTWC_PMIC_TMU - tristate "Intel BXT Whiskey Cove TMU Driver" - depends on REGMAP - depends on MFD_INTEL_PMC_BXT - depends on INTEL_SOC_PMIC_BXTWC - help - Select this driver to use Intel BXT 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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 03a1fc20bba5..bcdd75a48a80 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices -obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 4dd1fd4450ad..46db129bee7f 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -21,4 +21,14 @@ source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" +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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index dc6baf420808..dbdf4877ad10 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -8,3 +8,7 @@ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ + +# Intel PMIC / PMC / P-Unit drivers +intel_bxtwc_tmu-y := bxtwc_tmu.o +obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o 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 +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("BXT Whiskey Cove TMU Driver"); diff --git a/drivers/platform/x86/intel_bxtwc_tmu.c b/drivers/platform/x86/intel_bxtwc_tmu.c deleted file mode 100644 index 7ccf583649e6..000000000000 --- a/drivers/platform/x86/intel_bxtwc_tmu.c +++ /dev/null @@ -1,147 +0,0 @@ -// 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 -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("BXT Whiskey Cove TMU Driver"); -- cgit v1.2.3 From 2e4355e4c15e61729059434c975f81dd8e5bfefe Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:41 +0300 Subject: platform/x86: intel_chtdc_ti_pwrbtn: Move to intel sub-directory Move Intel Cherry Trail Dollar Cove TI power button driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-4-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 11 ---- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 11 ++++ drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/chtdc_ti_pwrbtn.c | 94 ++++++++++++++++++++++++++++ drivers/platform/x86/intel_chtdc_ti_pwrbtn.c | 94 ---------------------------- 6 files changed, 107 insertions(+), 106 deletions(-) create mode 100644 drivers/platform/x86/intel/chtdc_ti_pwrbtn.c delete mode 100644 drivers/platform/x86/intel_chtdc_ti_pwrbtn.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 084167d70f1b..ba0454450335 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1159,17 +1159,6 @@ config INTEL_UNCORE_FREQ_CONTROL To compile this driver as a module, choose M here: the module will be called intel-uncore-frequency. -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 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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index bcdd75a48a80..bf94af0749f5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices -obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 46db129bee7f..3792a5492a8a 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -31,4 +31,15 @@ config INTEL_BXTWC_PMIC_TMU 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index dbdf4877ad10..52d7bc0948f3 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -12,3 +12,5 @@ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ # 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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_chtdc_ti_pwrbtn.c b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c deleted file mode 100644 index 9606a994af22..000000000000 --- a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Power-button driver for Dollar Cove TI PMIC - * Copyright (C) 2014 Intel Corp - * Copyright (c) 2017 Takashi Iwai - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#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"); -- cgit v1.2.3 From f51c108d361c1f6f738049c3cc5d52734ea74424 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:42 +0300 Subject: platform/x86: intel_mrfld_pwrbtn: Move to intel sub-directory Move Intel Merrifield power button driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-5-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 11 --- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 11 +++ drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/mrfld_pwrbtn.c | 107 ++++++++++++++++++++++++++++++ drivers/platform/x86/intel_mrfld_pwrbtn.c | 107 ------------------------------ 6 files changed, 120 insertions(+), 119 deletions(-) create mode 100644 drivers/platform/x86/intel/mrfld_pwrbtn.c delete mode 100644 drivers/platform/x86/intel_mrfld_pwrbtn.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ba0454450335..2d6723bb6459 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1159,17 +1159,6 @@ config INTEL_UNCORE_FREQ_CONTROL To compile this driver as a module, choose M here: the module will be called intel-uncore-frequency. -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_PMC_CORE tristate "Intel PMC Core driver" depends on PCI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index bf94af0749f5..f70c0aa30cdd 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices -obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 3792a5492a8a..9e719db8450c 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -42,4 +42,15 @@ config INTEL_CHTDC_TI_PWRBTN 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 52d7bc0948f3..4ff755a11770 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -14,3 +14,5 @@ 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 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_mrfld_pwrbtn.c b/drivers/platform/x86/intel_mrfld_pwrbtn.c deleted file mode 100644 index d58fea51747e..000000000000 --- a/drivers/platform/x86/intel_mrfld_pwrbtn.c +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Power-button driver for Basin Cove PMIC - * - * Copyright (c) 2019, Intel Corporation. - * Author: Andy Shevchenko - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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"); -- cgit v1.2.3 From 386d17b22e422da3285e33fe8dcc282f02d98178 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:43 +0300 Subject: platform/x86: intel_punit_ipc: Move to intel sub-directory Move Intel P-Unit IPC driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-6-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 6 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 6 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/punit_ipc.c | 342 +++++++++++++++++++++++++++++++++ drivers/platform/x86/intel_punit_ipc.c | 342 --------------------------------- 7 files changed, 351 insertions(+), 350 deletions(-) create mode 100644 drivers/platform/x86/intel/punit_ipc.c delete mode 100644 drivers/platform/x86/intel_punit_ipc.c diff --git a/MAINTAINERS b/MAINTAINERS index b731988a4452..21813a0e00eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9475,7 +9475,7 @@ M: Zha Qipeng L: platform-driver-x86@vger.kernel.org S: Maintained F: arch/x86/include/asm/intel_punit_ipc.h -F: drivers/platform/x86/intel_punit_ipc.c +F: drivers/platform/x86/intel/punit_ipc.c INTEL PMC CORE DRIVER M: Rajneesh Bhardwaj diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2d6723bb6459..9cadce7908ea 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1180,12 +1180,6 @@ config INTEL_PMC_CORE - Low Power Mode registers (Tigerlake and beyond) - PMC quirks as needed to enable SLPS0/S0ix -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_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index f70c0aa30cdd..776370ce8fbb 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -128,7 +128,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o -obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 9e719db8450c..c4ceb5ee83f3 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -53,4 +53,10 @@ config INTEL_MRFLD_PWRBTN 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 4ff755a11770..58fc8e7a3b62 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -16,3 +16,5 @@ 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 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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 "); +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_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c deleted file mode 100644 index f58b8543f6ac..000000000000 --- a/drivers/platform/x86/intel_punit_ipc.c +++ /dev/null @@ -1,342 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* 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 "); -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); -- cgit v1.2.3 From fa082a7cf5a66a42a06da048afd972e2cc1b67ea Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:44 +0300 Subject: platform/x86: intel_pmc_core: Move to intel sub-directory Move Intel PMC core driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Reviewed-by: Rajneesh Bhardwaj Link: https://lore.kernel.org/r/20210820110458.73018-7-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 21 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/pmc/Kconfig | 25 + drivers/platform/x86/intel/pmc/Makefile | 9 + drivers/platform/x86/intel/pmc/core.c | 1859 ++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 346 +++++ drivers/platform/x86/intel/pmc/pltdrv.c | 80 ++ drivers/platform/x86/intel_pmc_core.c | 1859 -------------------------- drivers/platform/x86/intel_pmc_core.h | 346 ----- drivers/platform/x86/intel_pmc_core_pltdrv.c | 80 -- 13 files changed, 2322 insertions(+), 2308 deletions(-) create mode 100644 drivers/platform/x86/intel/pmc/Kconfig create mode 100644 drivers/platform/x86/intel/pmc/Makefile create mode 100644 drivers/platform/x86/intel/pmc/core.c create mode 100644 drivers/platform/x86/intel/pmc/core.h create mode 100644 drivers/platform/x86/intel/pmc/pltdrv.c delete mode 100644 drivers/platform/x86/intel_pmc_core.c delete mode 100644 drivers/platform/x86/intel_pmc_core.h delete mode 100644 drivers/platform/x86/intel_pmc_core_pltdrv.c diff --git a/MAINTAINERS b/MAINTAINERS index 21813a0e00eb..eefe4edb1b3d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9483,7 +9483,7 @@ M: David E Box L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-platform-intel-pmc -F: drivers/platform/x86/intel_pmc_core* +F: drivers/platform/x86/intel/pmc/ INTEL PMIC GPIO DRIVERS M: Andy Shevchenko diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9cadce7908ea..aed7570388ef 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1159,27 +1159,6 @@ config INTEL_UNCORE_FREQ_CONTROL To compile this driver as a module, choose M here: the module will be called intel-uncore-frequency. -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 - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 776370ce8fbb..1668f7360833 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -127,7 +127,6 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices -obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index c4ceb5ee83f3..7de11636904d 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -19,6 +19,7 @@ if X86_PLATFORM_DRIVERS_INTEL 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" config INTEL_BXTWC_PMIC_TMU diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 58fc8e7a3b62..a1555a1e421d 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -7,6 +7,7 @@ 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/ # Intel PMIC / PMC / P-Unit drivers 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..9963bc3d470c --- /dev/null +++ b/drivers/platform/x86/intel/pmc/core.c @@ -0,0 +1,1859 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Core SoC Power Management Controller Driver + * + * Copyright (c) 2016, Intel Corporation. + * All Rights Reserved. + * + * Authors: Rajneesh Bhardwaj + * Vishwanath Somayaji + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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); +} + +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, ®); + 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), + {} +}; + +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, 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) { + 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..b9bf3d3d6f7a --- /dev/null +++ b/drivers/platform/x86/intel/pmc/core.h @@ -0,0 +1,346 @@ +/* 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 + * Vishwanath Somayaji + */ + +#ifndef PMC_CORE_H +#define PMC_CORE_H + +#include + +#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 */ + +/* + * 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) + +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_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 +#include +#include + +#include +#include + +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_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c deleted file mode 100644 index ae410a358ffe..000000000000 --- a/drivers/platform/x86/intel_pmc_core.c +++ /dev/null @@ -1,1859 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Core SoC Power Management Controller Driver - * - * Copyright (c) 2016, Intel Corporation. - * All Rights Reserved. - * - * Authors: Rajneesh Bhardwaj - * Vishwanath Somayaji - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "intel_pmc_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); -} - -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, ®); - 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), - {} -}; - -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, 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) { - 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 deleted file mode 100644 index b9bf3d3d6f7a..000000000000 --- a/drivers/platform/x86/intel_pmc_core.h +++ /dev/null @@ -1,346 +0,0 @@ -/* 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 - * Vishwanath Somayaji - */ - -#ifndef PMC_CORE_H -#define PMC_CORE_H - -#include - -#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 */ - -/* - * 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) - -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_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_core_pltdrv.c b/drivers/platform/x86/intel_pmc_core_pltdrv.c deleted file mode 100644 index 73797680b895..000000000000 --- a/drivers/platform/x86/intel_pmc_core_pltdrv.c +++ /dev/null @@ -1,80 +0,0 @@ -// 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 -#include -#include - -#include -#include - -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"); -- cgit v1.2.3 From 2b6cb8f2e88b416393d2b34cad51bfe6e1aae8a7 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:46 +0300 Subject: platform/x86: intel_telemetry: Move to intel sub-directory Move Intel telemetry driver to intel sub-directory to improve readability. While at it, spell APL fully in the Kconfig. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Reviewed-by: Rajneesh Bhardwaj Link: https://lore.kernel.org/r/20210820110458.73018-9-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 12 - drivers/platform/x86/Makefile | 3 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/telemetry/Kconfig | 16 + drivers/platform/x86/intel/telemetry/Makefile | 11 + drivers/platform/x86/intel/telemetry/core.c | 450 +++++++++ drivers/platform/x86/intel/telemetry/debugfs.c | 961 +++++++++++++++++++ drivers/platform/x86/intel/telemetry/pltdrv.c | 1189 ++++++++++++++++++++++++ drivers/platform/x86/intel_telemetry_core.c | 450 --------- drivers/platform/x86/intel_telemetry_debugfs.c | 961 ------------------- drivers/platform/x86/intel_telemetry_pltdrv.c | 1189 ------------------------ 13 files changed, 2630 insertions(+), 2616 deletions(-) create mode 100644 drivers/platform/x86/intel/telemetry/Kconfig create mode 100644 drivers/platform/x86/intel/telemetry/Makefile create mode 100644 drivers/platform/x86/intel/telemetry/core.c create mode 100644 drivers/platform/x86/intel/telemetry/debugfs.c create mode 100644 drivers/platform/x86/intel/telemetry/pltdrv.c delete mode 100644 drivers/platform/x86/intel_telemetry_core.c delete mode 100644 drivers/platform/x86/intel_telemetry_debugfs.c delete mode 100644 drivers/platform/x86/intel_telemetry_pltdrv.c diff --git a/MAINTAINERS b/MAINTAINERS index eefe4edb1b3d..bb0fcf309cc8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9559,7 +9559,7 @@ M: "David E. Box" L: platform-driver-x86@vger.kernel.org S: Maintained F: arch/x86/include/asm/intel_telemetry.h -F: drivers/platform/x86/intel_telemetry* +F: drivers/platform/x86/intel/telemetry/ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index aed7570388ef..4e42ebac8892 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1206,18 +1206,6 @@ config INTEL_SCU_IPC_UTIL low level access for debug work and updating the firmware. Say N unless you will be doing this on an Intel MID platform. -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 APL 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. - endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 1668f7360833..bde20c13ceac 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -132,7 +132,4 @@ obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o -obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ - intel_telemetry_pltdrv.o \ - intel_telemetry_debugfs.o obj-$(CONFIG_PMC_ATOM) += pmc_atom.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 7de11636904d..379d9e425cc7 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -21,6 +21,7 @@ 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/telemetry/Kconfig" config INTEL_BXTWC_PMIC_TMU tristate "Intel Broxton Whiskey Cove TMU Driver" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index a1555a1e421d..e8c2b1249f87 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -9,6 +9,7 @@ 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_TELEMETRY) += telemetry/ # Intel PMIC / PMC / P-Unit drivers intel_bxtwc_tmu-y := bxtwc_tmu.o 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 +#include + +#include + +#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 "); +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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 "); +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 +#include +#include + +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_telemetry_core.c b/drivers/platform/x86/intel_telemetry_core.c deleted file mode 100644 index fdf55b5d6948..000000000000 --- a/drivers/platform/x86/intel_telemetry_core.c +++ /dev/null @@ -1,450 +0,0 @@ -// 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 -#include - -#include - -#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 "); -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 deleted file mode 100644 index 1d4d0fbfd63c..000000000000 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ /dev/null @@ -1,961 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#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 "); -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 deleted file mode 100644 index 405dea87de6b..000000000000 --- a/drivers/platform/x86/intel_telemetry_pltdrv.c +++ /dev/null @@ -1,1189 +0,0 @@ -// 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 -#include -#include - -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Intel SoC Telemetry Platform Driver"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From e6596c22744e7c3058bc3ef843d1a6c20632e27f Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:47 +0300 Subject: platform/x86: intel-rst: Move to intel sub-directory Move Intel RST driver to intel sub-directory to improve readability and rename it from intel-rst.c to rst.c. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-10-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 12 --- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-rst.c | 142 ------------------------------------ drivers/platform/x86/intel/Kconfig | 12 +++ drivers/platform/x86/intel/Makefile | 4 + drivers/platform/x86/intel/rst.c | 142 ++++++++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 155 deletions(-) delete mode 100644 drivers/platform/x86/intel-rst.c create mode 100644 drivers/platform/x86/intel/rst.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4e42ebac8892..a78678dede17 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1110,18 +1110,6 @@ config INTEL_IPS functionality. If in doubt, say Y here; it will only load on supported platforms. -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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index bde20c13ceac..2d8b2528b0ab 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -120,7 +120,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o -obj-$(CONFIG_INTEL_RST) += intel-rst.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o diff --git a/drivers/platform/x86/intel-rst.c b/drivers/platform/x86/intel-rst.c deleted file mode 100644 index 3b81cb896fed..000000000000 --- a/drivers/platform/x86/intel-rst.c +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2013 Matthew Garrett - */ - -#include -#include -#include - -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/Kconfig b/drivers/platform/x86/intel/Kconfig index 379d9e425cc7..fb1d1175fd09 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -61,4 +61,16 @@ config INTEL_PUNIT_IPC 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index e8c2b1249f87..d0ab3202c1ac 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -20,3 +20,7 @@ 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 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 + */ + +#include +#include +#include + +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); -- cgit v1.2.3 From 47bbe03eaf4493c1c7cc7ac9e24c633ef291f767 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:48 +0300 Subject: platform/x86: intel-smartconnect: Move to intel sub-directory Move Intel Smart Connect driver to intel sub-directory to improve readability and rename it from intel-smartconnect.c to smartconnect.c. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-11-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 14 ---------- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-smartconnect.c | 44 ------------------------------- drivers/platform/x86/intel/Kconfig | 14 ++++++++++ drivers/platform/x86/intel/Makefile | 2 ++ drivers/platform/x86/intel/smartconnect.c | 44 +++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 59 deletions(-) delete mode 100644 drivers/platform/x86/intel-smartconnect.c create mode 100644 drivers/platform/x86/intel/smartconnect.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a78678dede17..7820f4e63d01 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1110,20 +1110,6 @@ config INTEL_IPS functionality. If in doubt, say Y here; it will only load on supported platforms. -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. - source "drivers/platform/x86/intel_speed_select_if/Kconfig" config INTEL_TURBO_MAX_3 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 2d8b2528b0ab..44f990f7c2c4 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -120,7 +120,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o -obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o diff --git a/drivers/platform/x86/intel-smartconnect.c b/drivers/platform/x86/intel-smartconnect.c deleted file mode 100644 index 64c2dc93472f..000000000000 --- a/drivers/platform/x86/intel-smartconnect.c +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2013 Matthew Garrett - */ - -#include -#include - -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/Kconfig b/drivers/platform/x86/intel/Kconfig index fb1d1175fd09..24cd26579d52 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -73,4 +73,18 @@ config INTEL_RST 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index d0ab3202c1ac..cefcc92d93f5 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -24,3 +24,5 @@ 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 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 + */ + +#include +#include + +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); -- cgit v1.2.3 From 1fef1c047bfbeb140d109c9b7bd94410648650ca Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:49 +0300 Subject: platform/x86: intel_turbo_max_3: Move to intel sub-directory Move Intel Turbo Max 3 driver to intel sub-directory to improve readability and rename it from intel_turbo_max_3.c to turbo_max_3.c. Signed-off-by: Kate Hsuan Acked-by: Srinivas Pandruvada Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-12-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 10 --- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 11 +++ drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/turbo_max_3.c | 139 +++++++++++++++++++++++++++++++ drivers/platform/x86/intel_turbo_max_3.c | 139 ------------------------------- 6 files changed, 152 insertions(+), 150 deletions(-) create mode 100644 drivers/platform/x86/intel/turbo_max_3.c delete mode 100644 drivers/platform/x86/intel_turbo_max_3.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 7820f4e63d01..a9acd44c2076 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1112,16 +1112,6 @@ config INTEL_IPS source "drivers/platform/x86/intel_speed_select_if/Kconfig" -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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 44f990f7c2c4..15d0754363ea 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -121,7 +121,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ -obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 24cd26579d52..701479386305 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -87,4 +87,15 @@ config INTEL_SMARTCONNECT 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. + endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index cefcc92d93f5..1ecdf774e490 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -26,3 +26,5 @@ 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 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 + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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_turbo_max_3.c b/drivers/platform/x86/intel_turbo_max_3.c deleted file mode 100644 index 892140b62898..000000000000 --- a/drivers/platform/x86/intel_turbo_max_3.c +++ /dev/null @@ -1,139 +0,0 @@ -// 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 - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include - -#include -#include - -#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) -- cgit v1.2.3 From 075b559829d2b2f06d84d10f9712f6867b45c202 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:50 +0300 Subject: platform/x86: intel-uncore-frequency: Move to intel sub-directory Move Intel Uncore frequency driver to intel sub-directory to improve readability and rename it from intel-uncore-frequency.c to uncore-frequency.c. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-13-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 11 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-uncore-frequency.c | 451 -------------------------- drivers/platform/x86/intel/Kconfig | 12 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/uncore-frequency.c | 451 ++++++++++++++++++++++++++ 7 files changed, 466 insertions(+), 464 deletions(-) delete mode 100644 drivers/platform/x86/intel-uncore-frequency.c create mode 100644 drivers/platform/x86/intel/uncore-frequency.c diff --git a/MAINTAINERS b/MAINTAINERS index bb0fcf309cc8..5fe92c8e9214 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9565,7 +9565,7 @@ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-uncore-frequency.c +F: drivers/platform/x86/intel/uncore-frequency.c INTEL VIRTUAL BUTTON DRIVER M: AceLan Kao diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a9acd44c2076..dc861ec59c3a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1112,17 +1112,6 @@ config INTEL_IPS source "drivers/platform/x86/intel_speed_select_if/Kconfig" -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. - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 15d0754363ea..dfc5cde88999 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -121,7 +121,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ -obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/intel-uncore-frequency.c b/drivers/platform/x86/intel-uncore-frequency.c deleted file mode 100644 index 3ee4c5c8a64f..000000000000 --- a/drivers/platform/x86/intel-uncore-frequency.c +++ /dev/null @@ -1,451 +0,0 @@ -// 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 - */ - -#include -#include -#include -#include -#include -#include - -#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/Kconfig b/drivers/platform/x86/intel/Kconfig index 701479386305..7e3eb4404f7b 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -98,4 +98,16 @@ config INTEL_TURBO_MAX_3 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 1ecdf774e490..8080e850c1fb 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -28,3 +28,5 @@ 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/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 + */ + +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v1.2.3 From 6b1e482898e841b577d1d22b97fb65ac8ffacd9a Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:51 +0300 Subject: platform/x86: intel_speed_select_if: Move to intel sub-directory Move Intel Speed Select interface driver to intel sub-directory to improve readability and rename it from intel_speed_select_if to speed_select_if. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-14-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 2 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/speed_select_if/Kconfig | 17 + .../platform/x86/intel/speed_select_if/Makefile | 10 + .../x86/intel/speed_select_if/isst_if_common.c | 741 +++++++++++++++++++++ .../x86/intel/speed_select_if/isst_if_common.h | 72 ++ .../x86/intel/speed_select_if/isst_if_mbox_msr.c | 214 ++++++ .../x86/intel/speed_select_if/isst_if_mbox_pci.c | 227 +++++++ .../x86/intel/speed_select_if/isst_if_mmio.c | 200 ++++++ drivers/platform/x86/intel_speed_select_if/Kconfig | 17 - .../platform/x86/intel_speed_select_if/Makefile | 10 - .../x86/intel_speed_select_if/isst_if_common.c | 741 --------------------- .../x86/intel_speed_select_if/isst_if_common.h | 72 -- .../x86/intel_speed_select_if/isst_if_mbox_msr.c | 214 ------ .../x86/intel_speed_select_if/isst_if_mbox_pci.c | 227 ------- .../x86/intel_speed_select_if/isst_if_mmio.c | 200 ------ 19 files changed, 1484 insertions(+), 1485 deletions(-) create mode 100644 drivers/platform/x86/intel/speed_select_if/Kconfig create mode 100644 drivers/platform/x86/intel/speed_select_if/Makefile create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_common.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_common.h create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c create mode 100644 drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/Kconfig delete mode 100644 drivers/platform/x86/intel_speed_select_if/Makefile delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.h delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c delete mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c diff --git a/MAINTAINERS b/MAINTAINERS index 5fe92c8e9214..cf29f7154889 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9538,7 +9538,7 @@ INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_speed_select_if/ +F: drivers/platform/x86/intel/speed_select_if/ F: include/uapi/linux/isst_if.h F: tools/power/x86/intel-speed-select/ diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index dc861ec59c3a..935116ef2df9 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1110,8 +1110,6 @@ config INTEL_IPS functionality. If in doubt, say Y here; it will only load on supported platforms. -source "drivers/platform/x86/intel_speed_select_if/Kconfig" - config INTEL_SCU_IPC bool diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dfc5cde88999..dd3f1e683f0d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -120,7 +120,6 @@ obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o -obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 7e3eb4404f7b..2622653af37a 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -21,6 +21,7 @@ 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" config INTEL_BXTWC_PMIC_TMU diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 8080e850c1fb..f5ac4a5f953f 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -9,6 +9,7 @@ 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/ # Intel PMIC / PMC / P-Unit drivers 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..8a4d52a9028d --- /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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + */ + +#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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + */ + +#include +#include +#include +#include +#include +#include + +#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 + */ + +#include +#include +#include +#include +#include + +#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_speed_select_if/Kconfig b/drivers/platform/x86/intel_speed_select_if/Kconfig deleted file mode 100644 index ce3e3dc076d2..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/Kconfig +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 856076206f35..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# 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 deleted file mode 100644 index 8a4d52a9028d..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ /dev/null @@ -1,741 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: Common functions - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 deleted file mode 100644 index fdecdae248d7..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Intel Speed Select Interface: Drivers Internal defines - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#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 deleted file mode 100644 index 1b6eab071068..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c +++ /dev/null @@ -1,214 +0,0 @@ -// 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 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 deleted file mode 100644 index df1fc6c719f3..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c +++ /dev/null @@ -1,227 +0,0 @@ -// 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 - */ - -#include -#include -#include -#include -#include -#include - -#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 deleted file mode 100644 index ff49025ec085..000000000000 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Speed Select Interface: MMIO Interface - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Author: Srinivas Pandruvada - */ - -#include -#include -#include -#include -#include - -#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"); -- cgit v1.2.3 From 76693f5705828aa63c5cc52e407761964c6a4280 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:52 +0300 Subject: platform/x86: intel_atomisp2: Move to intel sub-directory Move Intel AtomISP v2 drivers to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-15-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 4 +- drivers/platform/x86/Kconfig | 34 ------- drivers/platform/x86/Makefile | 2 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/atomisp2/Kconfig | 43 ++++++++ drivers/platform/x86/intel/atomisp2/Makefile | 9 ++ drivers/platform/x86/intel/atomisp2/led.c | 116 ++++++++++++++++++++++ drivers/platform/x86/intel/atomisp2/pm.c | 143 +++++++++++++++++++++++++++ drivers/platform/x86/intel_atomisp2_led.c | 116 ---------------------- drivers/platform/x86/intel_atomisp2_pm.c | 143 --------------------------- 11 files changed, 315 insertions(+), 297 deletions(-) create mode 100644 drivers/platform/x86/intel/atomisp2/Kconfig create mode 100644 drivers/platform/x86/intel/atomisp2/Makefile create mode 100644 drivers/platform/x86/intel/atomisp2/led.c create mode 100644 drivers/platform/x86/intel/atomisp2/pm.c delete mode 100644 drivers/platform/x86/intel_atomisp2_led.c delete mode 100644 drivers/platform/x86/intel_atomisp2_pm.c diff --git a/MAINTAINERS b/MAINTAINERS index cf29f7154889..429b8b5c5283 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9212,13 +9212,13 @@ INTEL ATOMISP2 DUMMY / POWER-MANAGEMENT DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_atomisp2_pm.c +F: drivers/platform/x86/intel/atomisp2/pm.c INTEL ATOMISP2 LED DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel_atomisp2_led.c +F: drivers/platform/x86/intel/atomisp2/led.c INTEL BIOS SAR INT1092 DRIVER M: Shravan S diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 935116ef2df9..6de0d374ba16 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,40 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -config INTEL_ATOMISP2_LED - tristate "Intel AtomISP2 camera LED driver" - depends on GPIOLIB && LEDS_GPIO - 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 AtomISP2 dummy / power-management driver" - depends on PCI && IOSF_MBI && PM - depends on !INTEL_ATOMISP - 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. - config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dd3f1e683f0d..dccd7ecaae6a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,8 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o -obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 2622653af37a..9d0bc76a8948 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -16,6 +16,7 @@ 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" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index f5ac4a5f953f..080589b3082d 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -4,6 +4,7 @@ # 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/ 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + * + * 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 +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_atomisp2_led.c b/drivers/platform/x86/intel_atomisp2_led.c deleted file mode 100644 index 5935dfca166f..000000000000 --- a/drivers/platform/x86/intel_atomisp2_led.c +++ /dev/null @@ -1,116 +0,0 @@ -// 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 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 - * - * 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 -#include -#include -#include -#include -#include - -/* 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 "); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From c3d3586d12b106dd96c80febd571760a0b4095a5 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:53 +0300 Subject: platform/x86: intel-hid: Move to intel sub-directory Move Intel HID driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-16-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 13 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-hid.c | 732 ------------------------------------ drivers/platform/x86/intel/Kconfig | 13 + drivers/platform/x86/intel/Makefile | 4 + drivers/platform/x86/intel/hid.c | 732 ++++++++++++++++++++++++++++++++++++ 7 files changed, 750 insertions(+), 747 deletions(-) delete mode 100644 drivers/platform/x86/intel-hid.c create mode 100644 drivers/platform/x86/intel/hid.c diff --git a/MAINTAINERS b/MAINTAINERS index 429b8b5c5283..5339b210d6a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9321,7 +9321,7 @@ INTEL HID EVENT DRIVER M: Alex Hung L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-hid.c +F: drivers/platform/x86/intel/hid.c INTEL I/OAT DMA DRIVER M: Dave Jiang diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6de0d374ba16..6659b1036945 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,19 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/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_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" depends on GPIOLIB && ACPI && PM_SLEEP diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dccd7ecaae6a..1062773e8f41 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,7 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c deleted file mode 100644 index 2e4e97a626a5..000000000000 --- a/drivers/platform/x86/intel-hid.c +++ /dev/null @@ -1,732 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Intel HID event & 5 button array driver - * - * Copyright (C) 2015 Alex Hung - * Copyright (C) 2015 Andrew Lutomirski - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#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/Kconfig b/drivers/platform/x86/intel/Kconfig index 9d0bc76a8948..eb6ac7e52cd6 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -25,6 +25,19 @@ source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/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_BXTWC_PMIC_TMU tristate "Intel Broxton Whiskey Cove TMU Driver" depends on INTEL_SOC_PMIC_BXTWC diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 080589b3082d..d0b5ef290bcd 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -13,6 +13,10 @@ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ +# Intel input drivers +intel-hid-y := hid.o +obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o + # Intel PMIC / PMC / P-Unit drivers intel_bxtwc_tmu-y := bxtwc_tmu.o obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o 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 + * Copyright (C) 2015 Andrew Lutomirski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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); -- cgit v1.2.3 From daef4c5a042302a047e56e8985f8d50d85f45802 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:54 +0300 Subject: platform/x86: intel_int0002_vgpio: Move to intel sub-directory Move Intel vGPIO (INT0002) driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-17-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 19 -- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 19 ++ drivers/platform/x86/intel/Makefile | 4 + drivers/platform/x86/intel/int0002_vgpio.c | 294 +++++++++++++++++++++++++++++ drivers/platform/x86/intel_int0002_vgpio.c | 294 ----------------------------- 6 files changed, 317 insertions(+), 314 deletions(-) create mode 100644 drivers/platform/x86/intel/int0002_vgpio.c delete mode 100644 drivers/platform/x86/intel_int0002_vgpio.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6659b1036945..92d119b5dd61 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,25 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 1062773e8f41..52522f2841dd 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,7 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index eb6ac7e52cd6..cb953301fdd0 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -38,6 +38,25 @@ config INTEL_HID_EVENT To compile this driver as a module, choose M here: the module will be called intel_hid. +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_BXTWC_PMIC_TMU tristate "Intel Broxton Whiskey Cove TMU Driver" depends on INTEL_SOC_PMIC_BXTWC diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index d0b5ef290bcd..e8cb36353428 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -17,6 +17,10 @@ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ intel-hid-y := hid.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o +# Intel miscellaneous drivers +intel_int0002_vgpio-y := int0002_vgpio.o +obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o + # Intel PMIC / PMC / P-Unit drivers intel_bxtwc_tmu-y := bxtwc_tmu.o obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o 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 + * + * Loosely based on android x86 kernel code which is: + * + * Copyright (c) 2014, Intel Corporation. + * + * Author: Dyut Kumar Sil + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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? */ + 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 "); +MODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_int0002_vgpio.c b/drivers/platform/x86/intel_int0002_vgpio.c deleted file mode 100644 index 569342aa8926..000000000000 --- a/drivers/platform/x86/intel_int0002_vgpio.c +++ /dev/null @@ -1,294 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel INT0002 "Virtual GPIO" driver - * - * Copyright (C) 2017 Hans de Goede - * - * Loosely based on android x86 kernel code which is: - * - * Copyright (c) 2014, Intel Corporation. - * - * Author: Dyut Kumar Sil - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#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? */ - 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 "); -MODULE_DESCRIPTION("Intel INT0002 Virtual GPIO driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From cdbb8f5e79222dd964ebe3607ca541e1e799d9c8 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:55 +0300 Subject: platform/x86: intel_oaktrail: Move to intel sub-directory Move Intel Oaktrail driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-18-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 10 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel/Kconfig | 10 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/oaktrail.c | 377 ++++++++++++++++++++++++++++++++++ drivers/platform/x86/intel_oaktrail.c | 377 ---------------------------------- 6 files changed, 389 insertions(+), 388 deletions(-) create mode 100644 drivers/platform/x86/intel/oaktrail.c delete mode 100644 drivers/platform/x86/intel_oaktrail.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 92d119b5dd61..7f54db30cf5a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,16 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -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_VBTN tristate "INTEL VIRTUAL BUTTON" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 52522f2841dd..836fbab61f47 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,7 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # MSI diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index cb953301fdd0..3c7fd0bd39f1 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -57,6 +57,16 @@ config INTEL_INT0002_VGPIO 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 diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index e8cb36353428..f27d91464cc9 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -20,6 +20,8 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.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 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 + * , based on MSI driver + * Copyright (C) 2006 Lennart Poettering + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c deleted file mode 100644 index 1a09a75bd16d..000000000000 --- a/drivers/platform/x86/intel_oaktrail.c +++ /dev/null @@ -1,377 +0,0 @@ -// 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 - * , based on MSI driver - * Copyright (C) 2006 Lennart Poettering - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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"); -- cgit v1.2.3 From 3afeacfd39eadc1f5f19a992eff65f554bd3e717 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:56 +0300 Subject: platform/x86: intel-vbtn: Move to intel sub-directory Move Intel vButton driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-19-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 13 -- drivers/platform/x86/Makefile | 2 - drivers/platform/x86/intel-vbtn.c | 414 ------------------------------------ drivers/platform/x86/intel/Kconfig | 13 ++ drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/vbtn.c | 414 ++++++++++++++++++++++++++++++++++++ 7 files changed, 430 insertions(+), 430 deletions(-) delete mode 100644 drivers/platform/x86/intel-vbtn.c create mode 100644 drivers/platform/x86/intel/vbtn.c diff --git a/MAINTAINERS b/MAINTAINERS index 5339b210d6a5..4662d4d4db5d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9571,7 +9571,7 @@ INTEL VIRTUAL BUTTON DRIVER M: AceLan Kao L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel-vbtn.c +F: drivers/platform/x86/intel/vbtn.c INTEL WIRELESS 3945ABG/BG, 4965AGN (iwlegacy) M: Stanislaw Gruszka diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 7f54db30cf5a..f1349a1e17ae 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -668,19 +668,6 @@ config THINKPAD_LMI source "drivers/platform/x86/intel/Kconfig" -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 MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 836fbab61f47..e6e888af9df7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,8 +71,6 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ -obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - # MSI obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c deleted file mode 100644 index 309166431063..000000000000 --- a/drivers/platform/x86/intel-vbtn.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Intel Virtual Button driver for Windows 8.1+ - * - * Copyright (C) 2016 AceLan Kao - * Copyright (C) 2016 Alex Hung - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#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/Kconfig b/drivers/platform/x86/intel/Kconfig index 3c7fd0bd39f1..a5d71d669b82 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -38,6 +38,19 @@ config INTEL_HID_EVENT 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 diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index f27d91464cc9..67b16119e712 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -16,6 +16,8 @@ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ # 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 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 + * Copyright (C) 2016 Alex Hung + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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); -- cgit v1.2.3 From bd5b4fb47dde86b5e04686463dc2420e5ed6932a Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:57 +0300 Subject: platform/x86: intel-wmi-sbl-fw-update: Move to intel sub-directory Move Intel WMI Slim Bootloader FW update driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-20-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 10 -- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-wmi-sbl-fw-update.c | 144 ------------------------- drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/wmi/Kconfig | 18 ++++ drivers/platform/x86/intel/wmi/Makefile | 7 ++ drivers/platform/x86/intel/wmi/sbl-fw-update.c | 144 +++++++++++++++++++++++++ 9 files changed, 172 insertions(+), 156 deletions(-) delete mode 100644 drivers/platform/x86/intel-wmi-sbl-fw-update.c create mode 100644 drivers/platform/x86/intel/wmi/Kconfig create mode 100644 drivers/platform/x86/intel/wmi/Makefile create mode 100644 drivers/platform/x86/intel/wmi/sbl-fw-update.c diff --git a/MAINTAINERS b/MAINTAINERS index 4662d4d4db5d..3ca5102ed811 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9592,7 +9592,7 @@ M: Jithu Joseph R: Maurice Ma S: Maintained W: https://slimbootloader.github.io/security/firmware-update.html -F: drivers/platform/x86/intel-wmi-sbl-fw-update.c +F: drivers/platform/x86/intel/wmi/sbl-fw-update.c INTEL WMI THUNDERBOLT FORCE POWER DRIVER L: Dell.Client.Kernel@dell.com diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index f1349a1e17ae..d23053b6e29f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -77,16 +77,6 @@ config UV_SYSFS To compile this driver as a module, choose M here: the module will be called uv_sysfs. -config INTEL_WMI_SBL_FW_UPDATE - tristate "Intel WMI Slim Bootloader firmware update signaling driver" - depends on ACPI_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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e6e888af9df7..29ab09e0f36b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o -obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o diff --git a/drivers/platform/x86/intel-wmi-sbl-fw-update.c b/drivers/platform/x86/intel-wmi-sbl-fw-update.c deleted file mode 100644 index 3c86e0108a24..000000000000 --- a/drivers/platform/x86/intel-wmi-sbl-fw-update.c +++ /dev/null @@ -1,144 +0,0 @@ -// 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/. - * 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 -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index a5d71d669b82..0b21468e1bd0 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -24,6 +24,7 @@ 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" diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 67b16119e712..8b3a3f7bab49 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -12,6 +12,7 @@ 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 diff --git a/drivers/platform/x86/intel/wmi/Kconfig b/drivers/platform/x86/intel/wmi/Kconfig new file mode 100644 index 000000000000..c5753b1e8f43 --- /dev/null +++ b/drivers/platform/x86/intel/wmi/Kconfig @@ -0,0 +1,18 @@ +# 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. diff --git a/drivers/platform/x86/intel/wmi/Makefile b/drivers/platform/x86/intel/wmi/Makefile new file mode 100644 index 000000000000..bf1f118b6839 --- /dev/null +++ b/drivers/platform/x86/intel/wmi/Makefile @@ -0,0 +1,7 @@ +# 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 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/. + * 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 +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Slim Bootloader firmware update signaling driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 95c3e4b4282a4033ff3b0fe64bd4d2a2f3f0d31d Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 20 Aug 2021 14:04:58 +0300 Subject: platform/x86: intel-wmi-thunderbolt: Move to intel sub-directory Move Intel WMI Thunderbolt driver to intel sub-directory to improve readability. Signed-off-by: Kate Hsuan Reviewed-by: Hans de Goede Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210820110458.73018-21-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 12 ---- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel-wmi-thunderbolt.c | 94 ---------------------------- drivers/platform/x86/intel/wmi/Kconfig | 13 ++++ drivers/platform/x86/intel/wmi/Makefile | 2 + drivers/platform/x86/intel/wmi/thunderbolt.c | 94 ++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 108 deletions(-) delete mode 100644 drivers/platform/x86/intel-wmi-thunderbolt.c create mode 100644 drivers/platform/x86/intel/wmi/thunderbolt.c diff --git a/MAINTAINERS b/MAINTAINERS index 3ca5102ed811..60ee48c71c83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9597,7 +9597,7 @@ F: drivers/platform/x86/intel/wmi/sbl-fw-update.c INTEL WMI THUNDERBOLT FORCE POWER DRIVER L: Dell.Client.Kernel@dell.com S: Maintained -F: drivers/platform/x86/intel-wmi-thunderbolt.c +F: drivers/platform/x86/intel/wmi/thunderbolt.c INTEL WWAN IOSM DRIVER M: M Chetan Kumar diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d23053b6e29f..e21ea3d23e6f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -77,18 +77,6 @@ config UV_SYSFS To compile this driver as a module, choose M here: the module will be called uv_sysfs. -config INTEL_WMI_THUNDERBOLT - tristate "Intel WMI thunderbolt force power driver" - depends on ACPI_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. - config MXM_WMI tristate "WMI support for MXM Laptop Graphics" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 29ab09e0f36b..69690e26bb6d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -10,7 +10,6 @@ obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o -obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c deleted file mode 100644 index 4ae87060d18b..000000000000 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ /dev/null @@ -1,94 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/wmi/Kconfig b/drivers/platform/x86/intel/wmi/Kconfig index c5753b1e8f43..8e159f712179 100644 --- a/drivers/platform/x86/intel/wmi/Kconfig +++ b/drivers/platform/x86/intel/wmi/Kconfig @@ -16,3 +16,16 @@ config INTEL_WMI_SBL_FW_UPDATE 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 index bf1f118b6839..c2d56d25dea0 100644 --- a/drivers/platform/x86/intel/wmi/Makefile +++ b/drivers/platform/x86/intel/wmi/Makefile @@ -5,3 +5,5 @@ 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/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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Intel WMI Thunderbolt force power driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From bbab31101f44911b24c9da02733ce196e5702fea Mon Sep 17 00:00:00 2001 From: Gayatri Kammela Date: Mon, 16 Aug 2021 09:58:31 -0700 Subject: platform/x86/intel: pmc/core: Add Alderlake support to pmc core driver Add Alder Lake client and mobile support to pmc core driver. Cc: Chao Qin Cc: Srinivas Pandruvada Cc: Andy Shevchenko Cc: David Box Tested-by: You-Sheng Yang Acked-by: Rajneesh Bhardwaj Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Gayatri Kammela Link: https://lore.kernel.org/r/8b32e168f8e69dd00aabfb2e4383db78f22b123b.1629091915.git.gayatri.kammela@intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.c | 68 +++++++++++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 2 ++ 2 files changed, 70 insertions(+) diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 9963bc3d470c..81b8049c7023 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -645,6 +645,73 @@ 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_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, + .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, +}; + static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset) { return readl(pmcdev->regbase + reg_offset); @@ -1611,6 +1678,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = { 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), {} }; diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index b9bf3d3d6f7a..8972363b57b4 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -199,6 +199,8 @@ enum ppfear_regs { #define TGL_NUM_IP_IGN_ALLOWED 23 #define TGL_PMC_LPM_RES_COUNTER_STEP_X2 61 /* 30.5us * 2 */ +#define ADL_PMC_SLP_S0_RES_COUNTER_OFFSET 0x1098 + /* * Tigerlake Power Management Controller register offsets */ -- cgit v1.2.3 From ee7e89ff80063616c7f81b97ce7d38733019531a Mon Sep 17 00:00:00 2001 From: Gayatri Kammela Date: Mon, 16 Aug 2021 09:58:32 -0700 Subject: platform/x86/intel: pmc/core: Add Latency Tolerance Reporting (LTR) support to Alder Lake Add support to show the Latency Tolerance Reporting for the IPs on the Alder Lake PCH as reported by the PMC. This LTR support on Alder Lake is slightly different from the Cannon lake PCH that is being reused by all platforms till Tiger Lake. Cc: Chao Qin Cc: Srinivas Pandruvada Cc: Andy Shevchenko Cc: David Box Tested-by: You-Sheng Yang Acked-by: Rajneesh Bhardwaj Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Gayatri Kammela Link: https://lore.kernel.org/r/5ca3ea090b53a9bf918b055447ab5c8ef2925cc4.1629091915.git.gayatri.kammela@intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.c | 39 +++++++++++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 2 ++ 2 files changed, 41 insertions(+) diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 81b8049c7023..a5e8c5cca98d 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -699,10 +699,48 @@ static const struct pmc_bit_map *ext_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_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, @@ -710,6 +748,7 @@ static const struct pmc_reg_map adl_reg_map = { .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, }; static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset) diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 8972363b57b4..6863f4de3070 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -199,6 +199,8 @@ enum ppfear_regs { #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 /* -- cgit v1.2.3 From 6cfce3ef806c1d458a816db7e63a1c13571abf86 Mon Sep 17 00:00:00 2001 From: Gayatri Kammela Date: Mon, 16 Aug 2021 09:58:33 -0700 Subject: platform/x86/intel: pmc/core: Add Alder Lake low power mode support for pmc core Alder Lake has 14 status registers that are memory mapped. These registers show the status of the low power mode requirements. The registers are latched on every C10 entry or exit and on every s0ix.y entry/exit. Accessing these registers is useful for debugging any low power related activities. Thus, add debugfs entry to access low power mode status registers. Cc: Chao Qin Cc: Srinivas Pandruvada Cc: Andy Shevchenko Cc: David Box Tested-by: You-Sheng Yang Acked-by: Rajneesh Bhardwaj Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Gayatri Kammela Link: https://lore.kernel.org/r/d27ec98589a5aaa569bbce0e937ed03779fc0a22.1629091915.git.gayatri.kammela@intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.c | 194 ++++++++++++++++++++++++++++++++++ drivers/platform/x86/intel/pmc/core.h | 13 +++ 2 files changed, 207 insertions(+) diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index a5e8c5cca98d..42f49ad0793d 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -736,6 +736,189 @@ static const struct pmc_bit_map adl_ltr_show_map[] = { {} }; +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, @@ -749,6 +932,17 @@ static const struct pmc_reg_map adl_reg_map = { .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) diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 6863f4de3070..a46d3b53bf61 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -224,6 +224,18 @@ enum ppfear_regs { /* 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", @@ -283,6 +295,7 @@ struct pmc_reg_map { 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; -- cgit v1.2.3 From 66a91c00218c5f5d19e5bfaada05432c672b8241 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Mon, 16 Aug 2021 09:58:34 -0700 Subject: platform/x86/intel: pmc/core: Add GBE Package C10 fix for Alder Lake PCH Alder PCH uses the same Gigabit Ethernet (GBE) device as Tiger Lake PCH which cannot achieve PC10 without ignoring the PMC GBE LTR. Add this work around for Alder Lake PCH as well. Cc: Chao Qin Cc: Srinivas Pandruvada Cc: Andy Shevchenko Tested-by: You-Sheng Yang Acked-by: Rajneesh Bhardwaj Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: David E. Box Link: https://lore.kernel.org/r/9168e8bd687f2d0d5eb0ed116e08d0764eadf7b3.1629091915.git.gayatri.kammela@intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/pmc/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 42f49ad0793d..ac19fcc9abbf 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -2020,10 +2020,10 @@ static int pmc_core_probe(struct platform_device *pdev) pmc_core_get_tgl_lpm_reqs(pdev); /* - * On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when - * a cable is attached. Tell the PMC to ignore it. + * 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) { + 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); } -- cgit v1.2.3 From 0c59e612c0b6b94a0f1c5ccf7f4a4418dab77d97 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 22 Aug 2021 10:17:42 -0700 Subject: platform/mellanox: mlxbf-pmc: fix kernel-doc notation Fix kernel-doc warnings reported by the kernel test robot: drivers/platform/mellanox/mlxbf-pmc.c:82: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Structure to hold attribute and block info for each sysfs entry drivers/platform/mellanox/mlxbf-pmc.c:94: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Structure to hold info for each HW block drivers/platform/mellanox/mlxbf-pmc.c:121: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Structure to hold PMC context info drivers/platform/mellanox/mlxbf-pmc.c:148: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Structure to hold supported events for each block Also fix typos in a few struct member names. Signed-off-by: Randy Dunlap Reported-by: kernel test robot Cc: Aditya Srivastava Cc: Hans de Goede Cc: Mark Gross Cc: Vadim Pasternak Cc: platform-driver-x86@vger.kernel.org Link: https://lore.kernel.org/r/20210822171742.26921-1-rdunlap@infradead.org Signed-off-by: Hans de Goede --- drivers/platform/mellanox/mlxbf-pmc.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/platform/mellanox/mlxbf-pmc.c b/drivers/platform/mellanox/mlxbf-pmc.c index 35883984251f..04bc3b50aa7a 100644 --- a/drivers/platform/mellanox/mlxbf-pmc.c +++ b/drivers/platform/mellanox/mlxbf-pmc.c @@ -79,7 +79,8 @@ #define MLXBF_PMC_L3C_PERF_CNT_HIGH_VAL GENMASK(24, 0) /** - * Structure to hold attribute and block info for each sysfs entry + * struct mlxbf_pmc_attribute - Structure to hold attribute and block info + * for each sysfs entry * @dev_attr: Device attribute struct * @index: index to identify counter number within a block * @nr: block number to which the sysfs belongs @@ -91,7 +92,7 @@ struct mlxbf_pmc_attribute { }; /** - * Structure to hold info for each HW block + * struct mlxbf_pmc_block_info - Structure to hold info for each HW block * * @mmio_base: The VA at which the PMC block is mapped * @blk_size: Size of each mapped region @@ -102,7 +103,7 @@ struct mlxbf_pmc_attribute { * @attr_event_list: Attributes for "event_list" sysfs files * @attr_enable: Attributes for "enable" sysfs files * @block_attr: All attributes needed for the block - * @blcok_attr_grp: Attribute group for the block + * @block_attr_grp: Attribute group for the block */ struct mlxbf_pmc_block_info { void __iomem *mmio_base; @@ -118,7 +119,7 @@ struct mlxbf_pmc_block_info { }; /** - * Structure to hold PMC context info + * struct mlxbf_pmc_context - Structure to hold PMC context info * * @pdev: The kernel structure representing the device * @total_blocks: Total number of blocks @@ -127,7 +128,7 @@ struct mlxbf_pmc_block_info { * @block_name: Block name * @block: Block info * @groups: Attribute groups from each block - * @sv_sreg_support: Whether SMCs are used to access performance registers + * @svc_sreg_support: Whether SMCs are used to access performance registers * @sreg_tbl_perf: Secure register access table number * @event_set: Event set to use */ @@ -145,7 +146,7 @@ struct mlxbf_pmc_context { }; /** - * Structure to hold supported events for each block + * struct mlxbf_pmc_events - Structure to hold supported events for each block * @evt_num: Event number used to program counters * @evt_name: Name of the event */ -- cgit v1.2.3 From 34570a898eef01b5311bfc9c448877eb717d3285 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 23 Aug 2021 12:32:21 +0300 Subject: platform/x86: hp_accel: Remove _INI method call According to ACPI specification the _INI method must be called when device is enumerated first time. After that there is no need to repeat the procedure. Convert the lis3lv02d_acpi_init() to be a stub (Note, we may not remove it because it is called unconditionally by the accelerometer main driver). Signed-off-by: Andy Shevchenko Tested-by: Kai-Heng Feng Link: https://lore.kernel.org/r/20210823093222.19544-2-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/misc/lis3lv02d/lis3lv02d.h | 1 - drivers/platform/x86/hp_accel.c | 14 +------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/misc/lis3lv02d/lis3lv02d.h b/drivers/misc/lis3lv02d/lis3lv02d.h index 7ac788fae1b8..c394c0b08519 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d.h +++ b/drivers/misc/lis3lv02d/lis3lv02d.h @@ -271,7 +271,6 @@ struct lis3lv02d { int regs_size; u8 *reg_cache; bool regs_stored; - bool init_required; u8 odr_mask; /* ODR bit mask */ u8 whoami; /* indicates measurement precision */ s16 (*read_data) (struct lis3lv02d *lis3, int reg); diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 8c0867bda828..54a4addc7903 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -78,23 +78,14 @@ static const struct acpi_device_id lis3lv02d_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids); - /** - * lis3lv02d_acpi_init - ACPI _INI method: initialize the device. + * lis3lv02d_acpi_init - initialize the device for ACPI * @lis3: pointer to the device struct * * Returns 0 on success. */ static int lis3lv02d_acpi_init(struct lis3lv02d *lis3) { - struct acpi_device *dev = lis3->bus_priv; - if (!lis3->init_required) - return 0; - - if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI, - NULL, NULL) != AE_OK) - return -EINVAL; - return 0; } @@ -359,7 +350,6 @@ static int lis3lv02d_add(struct acpi_device *device) } /* call the core layer do its init */ - lis3_dev.init_required = true; ret = lis3lv02d_init_device(&lis3_dev); if (ret) return ret; @@ -407,14 +397,12 @@ static int lis3lv02d_suspend(struct device *dev) static int lis3lv02d_resume(struct device *dev) { - lis3_dev.init_required = false; lis3lv02d_poweron(&lis3_dev); return 0; } static int lis3lv02d_restore(struct device *dev) { - lis3_dev.init_required = true; lis3lv02d_poweron(&lis3_dev); return 0; } -- cgit v1.2.3 From 8ebcb6c94c712ee15679c8ee6a40598077b9a9af Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 23 Aug 2021 12:32:22 +0300 Subject: platform/x86: hp_accel: Convert to be a platform driver ACPI core in conjunction with platform driver core provides an infrastructure to enumerate ACPI devices. Use it in order to remove a lot of boilerplate code. Signed-off-by: Andy Shevchenko Tested-by: Kai-Heng Feng Link: https://lore.kernel.org/r/20210823093222.19544-3-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp_accel.c | 64 +++++++++-------------------------------- 1 file changed, 14 insertions(+), 50 deletions(-) diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 54a4addc7903..cc53f725c041 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -28,9 +28,6 @@ #include #include "../../misc/lis3lv02d/lis3lv02d.h" -#define DRIVER_NAME "hp_accel" -#define ACPI_MDPS_CLASS "accelerometer" - /* Delayed LEDs infrastructure ------------------------------------ */ /* Special LED class that can defer work */ @@ -269,30 +266,6 @@ static struct delayed_led_classdev hpled_led = { .set_brightness = hpled_set, }; -static acpi_status -lis3lv02d_get_resource(struct acpi_resource *resource, void *context) -{ - if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { - struct acpi_resource_extended_irq *irq; - u32 *device_irq = context; - - irq = &resource->data.extended_irq; - *device_irq = irq->interrupts[0]; - } - - return AE_OK; -} - -static void lis3lv02d_enum_resources(struct acpi_device *device) -{ - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - lis3lv02d_get_resource, &lis3_dev.irq); - if (ACPI_FAILURE(status)) - printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); -} - static bool hp_accel_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { @@ -322,23 +295,19 @@ static bool hp_accel_i8042_filter(unsigned char data, unsigned char str, return false; } -static int lis3lv02d_add(struct acpi_device *device) +static int lis3lv02d_probe(struct platform_device *device) { int ret; - if (!device) - return -EINVAL; - - lis3_dev.bus_priv = device; + lis3_dev.bus_priv = ACPI_COMPANION(&device->dev); lis3_dev.init = lis3lv02d_acpi_init; lis3_dev.read = lis3lv02d_acpi_read; lis3_dev.write = lis3lv02d_acpi_write; - strcpy(acpi_device_name(device), DRIVER_NAME); - strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); - device->driver_data = &lis3_dev; /* obtain IRQ number of our device from ACPI */ - lis3lv02d_enum_resources(device); + ret = platform_get_irq_optional(device, 0); + if (ret > 0) + lis3_dev.irq = ret; /* If possible use a "standard" axes order */ if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) { @@ -371,11 +340,8 @@ static int lis3lv02d_add(struct acpi_device *device) return ret; } -static int lis3lv02d_remove(struct acpi_device *device) +static int lis3lv02d_remove(struct platform_device *device) { - if (!device) - return -EINVAL; - i8042_remove_filter(hp_accel_i8042_filter); lis3lv02d_joystick_disable(&lis3_dev); lis3lv02d_poweroff(&lis3_dev); @@ -386,7 +352,6 @@ static int lis3lv02d_remove(struct acpi_device *device) return lis3lv02d_remove_fs(&lis3_dev); } - #ifdef CONFIG_PM_SLEEP static int lis3lv02d_suspend(struct device *dev) { @@ -422,17 +387,16 @@ static const struct dev_pm_ops hp_accel_pm = { #endif /* For the HP MDPS aka 3D Driveguard */ -static struct acpi_driver lis3lv02d_driver = { - .name = DRIVER_NAME, - .class = ACPI_MDPS_CLASS, - .ids = lis3lv02d_device_ids, - .ops = { - .add = lis3lv02d_add, - .remove = lis3lv02d_remove, +static struct platform_driver lis3lv02d_driver = { + .probe = lis3lv02d_probe, + .remove = lis3lv02d_remove, + .driver = { + .name = "hp_accel", + .pm = HP_ACCEL_PM, + .acpi_match_table = lis3lv02d_device_ids, }, - .drv.pm = HP_ACCEL_PM, }; -module_acpi_driver(lis3lv02d_driver); +module_platform_driver(lis3lv02d_driver); MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED."); MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); -- cgit v1.2.3 From b72067c64b226fc42fd5262cfbb675ec68fb4934 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 24 Aug 2021 14:36:54 +0300 Subject: platform/x86: asus-wmi: Delete impossible condition The "asus->throttle_thermal_policy_mode" variable is a u8 so it can't be negative. And we always verify that the value is valid before setting the policy mode so there is no need to check again here. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20210824113654.GA31143@kili Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index cc5811844012..679429d41264 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2179,9 +2179,6 @@ static int platform_profile_get(struct platform_profile_handler *pprof, tp = asus->throttle_thermal_policy_mode; - if (tp < 0) - return tp; - switch (tp) { case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT: *profile = PLATFORM_PROFILE_BALANCED; -- cgit v1.2.3 From 828857f6709f1b13582049a1ef84eadb07f50c05 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Wed, 25 Aug 2021 18:37:02 +0800 Subject: platform/x86: asus-wmi: Fix "unsigned 'retval' is never less than zero" smatch warning Eliminate the follow smatch warnings: drivers/platform/x86/asus-wmi.c:478 panel_od_write() warn: unsigned 'retval' is never less than zero. drivers/platform/x86/asus-wmi.c:566 panel_od_write() warn: unsigned 'retval' is never less than zero. drivers/platform/x86/asus-wmi.c:1451 panel_od_write() warn: unsigned 'retval' is never less than zero. Reported-by: Abaci Robot Fixes: 98829e84dc67 ("asus-wmi: Add dgpu disable method") Fixes: 382b91db8044 ("asus-wmi: Add egpu enable method") Fixes: ca91ea34778f ("asus-wmi: Add panel overdrive functionality") Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/1629887822-23918-1-git-send-email-jiapeng.chong@linux.alibaba.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-wmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 679429d41264..e14fb5fa7324 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -475,7 +475,7 @@ static int dgpu_disable_write(struct asus_wmi *asus) return err; } - if (retval > 1 || retval < 0) { + if (retval > 1) { pr_warn("Failed to set dgpu disable (retval): 0x%x\n", retval); return -EIO; } @@ -563,7 +563,7 @@ static int egpu_enable_write(struct asus_wmi *asus) return err; } - if (retval > 1 || retval < 0) { + if (retval > 1) { pr_warn("Failed to set egpu disable (retval): 0x%x\n", retval); return -EIO; } @@ -1448,7 +1448,7 @@ static int panel_od_write(struct asus_wmi *asus) return err; } - if (retval > 1 || retval < 0) { + if (retval > 1) { pr_warn("Failed to set panel overdrive (retval): 0x%x\n", retval); return -EIO; } -- cgit v1.2.3 From 55879dc4d095232609fe81498c1b43f042708eef Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 Aug 2021 10:23:57 +0300 Subject: platform/x86: ISST: use semi-colons instead of commas The code works the same either way, but it's better to use semi-colons to separate statements. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20210825072357.GA12957@kili Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/speed_select_if/isst_if_common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 8a4d52a9028d..c9a85eb2e860 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -265,9 +265,9 @@ 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.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; -- cgit v1.2.3 From fb49d9946f96081f9a05d8f305b3f40285afe4a9 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 25 Aug 2021 09:07:49 -0700 Subject: platform/x86: dell-smbios-wmi: Avoid false-positive memcpy() warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for FORTIFY_SOURCE performing compile-time and run-time field bounds checking for memcpy(), memmove(), and memset(), avoid intentionally writing across neighboring fields. Since all the size checking has already happened, use input.pointer (void *) so memcpy() doesn't get confused about how much is being written. Avoids this false-positive warning when run-time memcpy() strict bounds checking is enabled: memcpy: detected field-spanning write (size 4096) of single field (size 36) WARNING: CPU: 0 PID: 357 at drivers/platform/x86/dell/dell-smbios-wmi.c:74 run_smbios_call+0x110/0x1e0 [dell_smbios] Cc: Hans de Goede Cc: Mark Gross Cc: Mario Limonciello Cc: "Pali Rohár" Cc: Andy Shevchenko Cc: "Uwe Kleine-König" Cc: Dell.Client.Kernel@dell.com Cc: platform-driver-x86@vger.kernel.org Reported-by: Andy Lavr Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20210825160749.3891090-1-keescook@chromium.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/dell-smbios-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 33f823772733..01ea4bb958af 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -71,7 +71,7 @@ static int run_smbios_call(struct wmi_device *wdev) obj->integer.value); return -EIO; } - memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); + memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length); dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", priv->buf->std.output[0], priv->buf->std.output[1], priv->buf->std.output[2], priv->buf->std.output[3]); -- cgit v1.2.3 From 0487d4fc42d7f31a56cfd9e2237f9ebd889e6112 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 26 Aug 2021 16:08:22 +0200 Subject: platform/x86: dell-smbios-wmi: Add missing kfree in error-exit from run_smbios_call As pointed out be Kees Cook if we return -EIO because the obj->type != ACPI_TYPE_BUFFER, then we must kfree the output buffer before the return. Fixes: 1a258e670434 ("platform/x86: dell-smbios-wmi: Add new WMI dispatcher driver") Reported-by: Kees Cook Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210826140822.71198-1-hdegoede@redhat.com --- drivers/platform/x86/dell/dell-smbios-wmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c index 01ea4bb958af..931cc50136de 100644 --- a/drivers/platform/x86/dell/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -69,6 +69,7 @@ static int run_smbios_call(struct wmi_device *wdev) if (obj->type == ACPI_TYPE_INTEGER) dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", obj->integer.value); + kfree(output.pointer); return -EIO; } memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length); -- cgit v1.2.3