aa47ad3f85
Adds PMT Telemetry aggregator support for the DG1 graphics PCIe card. The device does not have the DVSEC region in its PCI config space so hard code the discovery table data in the driver. Also requires a fix for DG1 in the Telemetry driver for how the ACCESS_TYPE field is used. Signed-off-by: David E. Box <david.e.box@linux.intel.com> Reviewed-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
262 lines
6.2 KiB
C
262 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Intel Platform Monitoring Technology PMT driver
|
|
*
|
|
* Copyright (c) 2020, Intel Corporation.
|
|
* All Rights Reserved.
|
|
*
|
|
* Author: David E. Box <david.e.box@linux.intel.com>
|
|
*/
|
|
|
|
#include <linux/bits.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mfd/core.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/types.h>
|
|
|
|
/* 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) & GENMASK(31, 3))
|
|
#define INTEL_DVSEC_ENTRY_SIZE 4
|
|
|
|
/* PMT capabilities */
|
|
#define DVSEC_INTEL_ID_TELEMETRY 2
|
|
#define DVSEC_INTEL_ID_WATCHER 3
|
|
#define DVSEC_INTEL_ID_CRASHLOG 4
|
|
|
|
struct intel_dvsec_header {
|
|
u16 length;
|
|
u16 id;
|
|
u8 num_entries;
|
|
u8 entry_size;
|
|
u8 tbir;
|
|
u32 offset;
|
|
};
|
|
|
|
enum pmt_quirks {
|
|
/* Watcher capability not supported */
|
|
PMT_QUIRK_NO_WATCHER = BIT(0),
|
|
|
|
/* Crashlog capability not supported */
|
|
PMT_QUIRK_NO_CRASHLOG = BIT(1),
|
|
|
|
/* Use shift instead of mask to read discovery table offset */
|
|
PMT_QUIRK_TABLE_SHIFT = BIT(2),
|
|
|
|
/* DVSEC not present (provided in driver data) */
|
|
PMT_QUIRK_NO_DVSEC = BIT(3),
|
|
};
|
|
|
|
struct pmt_platform_info {
|
|
unsigned long quirks;
|
|
struct intel_dvsec_header **capabilities;
|
|
};
|
|
|
|
static const struct pmt_platform_info tgl_info = {
|
|
.quirks = PMT_QUIRK_NO_WATCHER | PMT_QUIRK_NO_CRASHLOG |
|
|
PMT_QUIRK_TABLE_SHIFT,
|
|
};
|
|
|
|
/* DG1 Platform with DVSEC quirk*/
|
|
static struct intel_dvsec_header dg1_telemetry = {
|
|
.length = 0x10,
|
|
.id = 2,
|
|
.num_entries = 1,
|
|
.entry_size = 3,
|
|
.tbir = 0,
|
|
.offset = 0x466000,
|
|
};
|
|
|
|
static struct intel_dvsec_header *dg1_capabilities[] = {
|
|
&dg1_telemetry,
|
|
NULL
|
|
};
|
|
|
|
static const struct pmt_platform_info dg1_info = {
|
|
.quirks = PMT_QUIRK_NO_DVSEC,
|
|
.capabilities = dg1_capabilities,
|
|
};
|
|
|
|
static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
|
|
unsigned long quirks)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res, *tmp;
|
|
struct mfd_cell *cell;
|
|
const char *name;
|
|
int count = header->num_entries;
|
|
int size = header->entry_size;
|
|
int id = header->id;
|
|
int i;
|
|
|
|
switch (id) {
|
|
case DVSEC_INTEL_ID_TELEMETRY:
|
|
name = "pmt_telemetry";
|
|
break;
|
|
case DVSEC_INTEL_ID_WATCHER:
|
|
if (quirks & PMT_QUIRK_NO_WATCHER) {
|
|
dev_info(dev, "Watcher not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
name = "pmt_watcher";
|
|
break;
|
|
case DVSEC_INTEL_ID_CRASHLOG:
|
|
if (quirks & PMT_QUIRK_NO_CRASHLOG) {
|
|
dev_info(dev, "Crashlog not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
name = "pmt_crashlog";
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!header->num_entries || !header->entry_size) {
|
|
dev_err(dev, "Invalid count or size for %s header\n", name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cell = devm_kzalloc(dev, sizeof(*cell), GFP_KERNEL);
|
|
if (!cell)
|
|
return -ENOMEM;
|
|
|
|
res = devm_kcalloc(dev, count, sizeof(*res), GFP_KERNEL);
|
|
if (!res)
|
|
return -ENOMEM;
|
|
|
|
if (quirks & PMT_QUIRK_TABLE_SHIFT)
|
|
header->offset >>= 3;
|
|
|
|
/*
|
|
* The PMT DVSEC contains the starting offset and count for a block of
|
|
* discovery tables, each providing access to monitoring facilities for
|
|
* a section of the device. Create a resource list of these tables to
|
|
* provide to the driver.
|
|
*/
|
|
for (i = 0, tmp = res; i < count; i++, tmp++) {
|
|
tmp->start = pdev->resource[header->tbir].start +
|
|
header->offset + i * (size << 2);
|
|
tmp->end = tmp->start + (size << 2) - 1;
|
|
tmp->flags = IORESOURCE_MEM;
|
|
}
|
|
|
|
cell->resources = res;
|
|
cell->num_resources = count;
|
|
cell->name = name;
|
|
|
|
return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cell, 1, NULL, 0,
|
|
NULL);
|
|
}
|
|
|
|
static int pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
{
|
|
struct pmt_platform_info *info;
|
|
unsigned long quirks = 0;
|
|
bool found_devices = false;
|
|
int ret, pos = 0;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
info = (struct pmt_platform_info *)id->driver_data;
|
|
|
|
if (info)
|
|
quirks = info->quirks;
|
|
|
|
if (info && (info->quirks & PMT_QUIRK_NO_DVSEC)) {
|
|
struct intel_dvsec_header **header;
|
|
|
|
header = info->capabilities;
|
|
while (*header) {
|
|
ret = pmt_add_dev(pdev, *header, quirks);
|
|
if (ret)
|
|
dev_warn(&pdev->dev,
|
|
"Failed to add device for DVSEC id %d\n",
|
|
(*header)->id);
|
|
else
|
|
found_devices = true;
|
|
|
|
++header;
|
|
}
|
|
} else {
|
|
do {
|
|
struct intel_dvsec_header header;
|
|
u32 table;
|
|
u16 vid;
|
|
|
|
pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
|
|
if (!pos)
|
|
break;
|
|
|
|
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);
|
|
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, quirks);
|
|
if (ret)
|
|
continue;
|
|
|
|
found_devices = true;
|
|
} while (true);
|
|
}
|
|
|
|
if (!found_devices)
|
|
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_ADL 0x467d
|
|
#define PCI_DEVICE_ID_INTEL_PMT_DG1 0x490e
|
|
#define PCI_DEVICE_ID_INTEL_PMT_OOBMSM 0x09a7
|
|
#define PCI_DEVICE_ID_INTEL_PMT_TGL 0x9a0d
|
|
static const struct pci_device_id pmt_pci_ids[] = {
|
|
{ PCI_DEVICE_DATA(INTEL, PMT_ADL, &tgl_info) },
|
|
{ PCI_DEVICE_DATA(INTEL, PMT_DG1, &dg1_info) },
|
|
{ PCI_DEVICE_DATA(INTEL, PMT_OOBMSM, NULL) },
|
|
{ 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 PMT driver");
|
|
MODULE_LICENSE("GPL v2");
|