@@ -8733,6 +8733,11 @@ F: drivers/mfd/intel_soc_pmic*
F: include/linux/mfd/intel_msic.h
F: include/linux/mfd/intel_soc_pmic*
+INTEL PMT DRIVER
+M: "David E. Box" <david.e.box@linux.intel.com>
+S: Maintained
+F: drivers/mfd/intel_pmt.c
+
INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT
M: Stanislav Yakovlev <stas.yakovlev@gmail.com>
L: linux-wireless@vger.kernel.org
@@ -632,6 +632,16 @@ config MFD_INTEL_MSIC
Passage) chip. This chip embeds audio, battery, GPIO, etc.
devices used in Intel Medfield platforms.
+config MFD_INTEL_PMT
+ tristate "Intel Platform Monitoring Technology support"
+ depends on PCI
+ select MFD_CORE
+ help
+ The Intel Platform Monitoring Technology (PMT) is an interface that
+ provides access to hardware monitor registers. This driver supports
+ Telemetry, Watcher, and Crashlog PMT capabilities/devices for
+ platforms starting from Tiger Lake.
+
config MFD_IPAQ_MICRO
bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support"
depends on SA1100_H3100 || SA1100_H3600
@@ -212,6 +212,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS) += intel-lpss.o
obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o
obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o
obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
+obj-$(CONFIG_MFD_INTEL_PMT) += intel_pmt.o
obj-$(CONFIG_MFD_PALMAS) += palmas.o
obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
new file mode 100644
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitoring Technology MFD driver
+ *
+ * Copyright (c) 2020, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Authors: David E. Box <david.e.box@linux.intel.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/mfd/core.h>
+#include <linux/intel-dvsec.h>
+
+static const struct pmt_platform_info tgl_info = {
+ .quirks = PMT_QUIRK_NO_WATCHER | PMT_QUIRK_NO_CRASHLOG,
+};
+
+static int
+pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
+ struct pmt_platform_info *info)
+{
+ struct mfd_cell *cell, *tmp;
+ const char *name;
+ int i;
+
+ switch (header->id) {
+ case DVSEC_INTEL_ID_TELEM:
+ name = TELEM_DEV_NAME;
+ break;
+ case DVSEC_INTEL_ID_WATCHER:
+ if (info->quirks && PMT_QUIRK_NO_WATCHER) {
+ dev_info(&pdev->dev, "Watcher not supported\n");
+ return 0;
+ }
+ name = WATCHER_DEV_NAME;
+ break;
+ case DVSEC_INTEL_ID_CRASHLOG:
+ if (info->quirks && PMT_QUIRK_NO_WATCHER) {
+ dev_info(&pdev->dev, "Crashlog not supported\n");
+ return 0;
+ }
+ name = CRASHLOG_DEV_NAME;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cell = devm_kcalloc(&pdev->dev, header->num_entries,
+ sizeof(*cell), GFP_KERNEL);
+ if (!cell)
+ return -ENOMEM;
+
+ /* Create a platform device for each entry. */
+ for (i = 0, tmp = cell; i < header->num_entries; i++, tmp++) {
+ struct resource *res;
+
+ res = devm_kzalloc(&pdev->dev, sizeof(*res), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+
+ tmp->name = name;
+
+ res->start = pdev->resource[header->tbir].start +
+ header->offset +
+ (i * (INTEL_DVSEC_ENTRY_SIZE << 2));
+ res->end = res->start + (header->entry_size << 2) - 1;
+ res->flags = IORESOURCE_MEM;
+
+ tmp->resources = res;
+ tmp->num_resources = 1;
+ tmp->platform_data = header;
+ tmp->pdata_size = sizeof(*header);
+
+ }
+
+ return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, cell,
+ header->num_entries, NULL, 0, NULL);
+}
+
+static int
+pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ u16 vid;
+ u32 table;
+ int ret, pos = 0, last_pos = 0;
+ struct pmt_platform_info *info;
+ struct intel_dvsec_header header;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ info = devm_kmemdup(&pdev->dev, (void *)id->driver_data, sizeof(*info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ while ((pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC))) {
+ pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
+ if (vid != PCI_VENDOR_ID_INTEL)
+ continue;
+
+ pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
+ &header.id);
+
+ pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
+ &header.num_entries);
+
+ pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
+ &header.entry_size);
+
+ if (!header.num_entries || !header.entry_size)
+ return -EINVAL;
+
+ pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
+ &table);
+
+ header.tbir = INTEL_DVSEC_TABLE_BAR(table);
+ header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
+ ret = pmt_add_dev(pdev, &header, info);
+ if (ret)
+ dev_warn(&pdev->dev,
+ "Failed to add devices for DVSEC id %d\n",
+ header.id);
+ last_pos = pos;
+ }
+
+ if (!last_pos) {
+ dev_err(&pdev->dev, "No supported PMT capabilities found.\n");
+ return -ENODEV;
+ }
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_allow(&pdev->dev);
+
+ return 0;
+}
+
+static void pmt_pci_remove(struct pci_dev *pdev)
+{
+ pm_runtime_forbid(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+}
+
+#define PCI_DEVICE_ID_INTEL_PMT_TGL 0x9a0d
+
+static const struct pci_device_id pmt_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, PMT_TGL, &tgl_info) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, pmt_pci_ids);
+
+static struct pci_driver pmt_pci_driver = {
+ .name = "intel-pmt",
+ .id_table = pmt_pci_ids,
+ .probe = pmt_pci_probe,
+ .remove = pmt_pci_remove,
+};
+
+module_pci_driver(pmt_pci_driver);
+
+MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
+MODULE_DESCRIPTION("Intel Platform Monitoring Technology MFD driver");
+MODULE_LICENSE("GPL v2");
new file mode 100644
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef INTEL_DVSEC_H
+#define INTEL_DVSEC_H
+
+#include <linux/types.h>
+
+#define DVSEC_INTEL_ID_TELEM 2
+#define DVSEC_INTEL_ID_WATCHER 3
+#define DVSEC_INTEL_ID_CRASHLOG 4
+
+#define TELEM_DEV_NAME "pmt_telemetry"
+#define WATCHER_DEV_NAME "pmt_watcher"
+#define CRASHLOG_DEV_NAME "pmt_crashlog"
+
+/* Intel DVSEC capability vendor space offsets */
+#define INTEL_DVSEC_ENTRIES 0xA
+#define INTEL_DVSEC_SIZE 0xB
+#define INTEL_DVSEC_TABLE 0xC
+#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
+#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) >> 3)
+
+#define INTEL_DVSEC_ENTRY_SIZE 4
+
+/* DVSEC header */
+struct intel_dvsec_header {
+ u16 length;
+ u16 id;
+ u8 num_entries;
+ u8 entry_size;
+ u8 entry_max;
+ u8 tbir;
+ u32 offset;
+};
+
+enum pmt_quirks {
+ /* Watcher capability not supported */
+ PMT_QUIRK_NO_WATCHER = (1 << 0),
+
+ /* Crashlog capability not supported */
+ PMT_QUIRK_NO_CRASHLOG = (1 << 1),
+};
+
+struct pmt_platform_info {
+ unsigned long quirks;
+ struct intel_dvsec_header **capabilities;
+};
+
+#endif