Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Intel Platform Monitoring Technology PMT driver
  4 *
  5 * Copyright (c) 2020, Intel Corporation.
  6 * All Rights Reserved.
  7 *
  8 * Author: David E. Box <david.e.box@linux.intel.com>
  9 */
 10
 11#include <linux/bits.h>
 12#include <linux/kernel.h>
 13#include <linux/mfd/core.h>
 14#include <linux/module.h>
 15#include <linux/pci.h>
 16#include <linux/platform_device.h>
 17#include <linux/pm.h>
 18#include <linux/pm_runtime.h>
 19#include <linux/types.h>
 20
 21/* Intel DVSEC capability vendor space offsets */
 22#define INTEL_DVSEC_ENTRIES		0xA
 23#define INTEL_DVSEC_SIZE		0xB
 24#define INTEL_DVSEC_TABLE		0xC
 25#define INTEL_DVSEC_TABLE_BAR(x)	((x) & GENMASK(2, 0))
 26#define INTEL_DVSEC_TABLE_OFFSET(x)	((x) & GENMASK(31, 3))
 27#define INTEL_DVSEC_ENTRY_SIZE		4
 28
 29/* PMT capabilities */
 30#define DVSEC_INTEL_ID_TELEMETRY	2
 31#define DVSEC_INTEL_ID_WATCHER		3
 32#define DVSEC_INTEL_ID_CRASHLOG		4
 33
 34struct intel_dvsec_header {
 35	u16	length;
 36	u16	id;
 37	u8	num_entries;
 38	u8	entry_size;
 39	u8	tbir;
 40	u32	offset;
 41};
 42
 43enum pmt_quirks {
 44	/* Watcher capability not supported */
 45	PMT_QUIRK_NO_WATCHER	= BIT(0),
 46
 47	/* Crashlog capability not supported */
 48	PMT_QUIRK_NO_CRASHLOG	= BIT(1),
 49
 50	/* Use shift instead of mask to read discovery table offset */
 51	PMT_QUIRK_TABLE_SHIFT	= BIT(2),
 52
 53	/* DVSEC not present (provided in driver data) */
 54	PMT_QUIRK_NO_DVSEC	= BIT(3),
 55};
 56
 57struct pmt_platform_info {
 58	unsigned long quirks;
 59	struct intel_dvsec_header **capabilities;
 60};
 61
 62static const struct pmt_platform_info tgl_info = {
 63	.quirks = PMT_QUIRK_NO_WATCHER | PMT_QUIRK_NO_CRASHLOG |
 64		  PMT_QUIRK_TABLE_SHIFT,
 65};
 66
 67/* DG1 Platform with DVSEC quirk*/
 68static struct intel_dvsec_header dg1_telemetry = {
 69	.length = 0x10,
 70	.id = 2,
 71	.num_entries = 1,
 72	.entry_size = 3,
 73	.tbir = 0,
 74	.offset = 0x466000,
 75};
 76
 77static struct intel_dvsec_header *dg1_capabilities[] = {
 78	&dg1_telemetry,
 79	NULL
 80};
 81
 82static const struct pmt_platform_info dg1_info = {
 83	.quirks = PMT_QUIRK_NO_DVSEC,
 84	.capabilities = dg1_capabilities,
 85};
 86
 87static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
 88		       unsigned long quirks)
 89{
 90	struct device *dev = &pdev->dev;
 91	struct resource *res, *tmp;
 92	struct mfd_cell *cell;
 93	const char *name;
 94	int count = header->num_entries;
 95	int size = header->entry_size;
 96	int id = header->id;
 97	int i;
 98
 99	switch (id) {
100	case DVSEC_INTEL_ID_TELEMETRY:
101		name = "pmt_telemetry";
102		break;
103	case DVSEC_INTEL_ID_WATCHER:
104		if (quirks & PMT_QUIRK_NO_WATCHER) {
105			dev_info(dev, "Watcher not supported\n");
106			return -EINVAL;
107		}
108		name = "pmt_watcher";
109		break;
110	case DVSEC_INTEL_ID_CRASHLOG:
111		if (quirks & PMT_QUIRK_NO_CRASHLOG) {
112			dev_info(dev, "Crashlog not supported\n");
113			return -EINVAL;
114		}
115		name = "pmt_crashlog";
116		break;
117	default:
118		return -EINVAL;
119	}
120
121	if (!header->num_entries || !header->entry_size) {
122		dev_err(dev, "Invalid count or size for %s header\n", name);
123		return -EINVAL;
124	}
125
126	cell = devm_kzalloc(dev, sizeof(*cell), GFP_KERNEL);
127	if (!cell)
128		return -ENOMEM;
129
130	res = devm_kcalloc(dev, count, sizeof(*res), GFP_KERNEL);
131	if (!res)
132		return -ENOMEM;
133
134	if (quirks & PMT_QUIRK_TABLE_SHIFT)
135		header->offset >>= 3;
136
137	/*
138	 * The PMT DVSEC contains the starting offset and count for a block of
139	 * discovery tables, each providing access to monitoring facilities for
140	 * a section of the device. Create a resource list of these tables to
141	 * provide to the driver.
142	 */
143	for (i = 0, tmp = res; i < count; i++, tmp++) {
144		tmp->start = pdev->resource[header->tbir].start +
145			     header->offset + i * (size << 2);
146		tmp->end = tmp->start + (size << 2) - 1;
147		tmp->flags = IORESOURCE_MEM;
148	}
149
150	cell->resources = res;
151	cell->num_resources = count;
152	cell->name = name;
153
154	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cell, 1, NULL, 0,
155				    NULL);
156}
157
158static int pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
159{
160	struct pmt_platform_info *info;
161	unsigned long quirks = 0;
162	bool found_devices = false;
163	int ret, pos = 0;
164
165	ret = pcim_enable_device(pdev);
166	if (ret)
167		return ret;
168
169	info = (struct pmt_platform_info *)id->driver_data;
170
171	if (info)
172		quirks = info->quirks;
173
174	if (info && (info->quirks & PMT_QUIRK_NO_DVSEC)) {
175		struct intel_dvsec_header **header;
176
177		header = info->capabilities;
178		while (*header) {
179			ret = pmt_add_dev(pdev, *header, quirks);
180			if (ret)
181				dev_warn(&pdev->dev,
182					 "Failed to add device for DVSEC id %d\n",
183					 (*header)->id);
184			else
185				found_devices = true;
186
187			++header;
188		}
189	} else {
190		do {
191			struct intel_dvsec_header header;
192			u32 table;
193			u16 vid;
194
195			pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
196			if (!pos)
197				break;
198
199			pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
200			if (vid != PCI_VENDOR_ID_INTEL)
201				continue;
202
203			pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
204					     &header.id);
205			pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
206					     &header.num_entries);
207			pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
208					     &header.entry_size);
209			pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
210					      &table);
211
212			header.tbir = INTEL_DVSEC_TABLE_BAR(table);
213			header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
214
215			ret = pmt_add_dev(pdev, &header, quirks);
216			if (ret)
217				continue;
218
219			found_devices = true;
220		} while (true);
221	}
222
223	if (!found_devices)
224		return -ENODEV;
225
226	pm_runtime_put(&pdev->dev);
227	pm_runtime_allow(&pdev->dev);
228
229	return 0;
230}
231
232static void pmt_pci_remove(struct pci_dev *pdev)
233{
234	pm_runtime_forbid(&pdev->dev);
235	pm_runtime_get_sync(&pdev->dev);
236}
237
238#define PCI_DEVICE_ID_INTEL_PMT_ADL	0x467d
239#define PCI_DEVICE_ID_INTEL_PMT_DG1	0x490e
240#define PCI_DEVICE_ID_INTEL_PMT_OOBMSM	0x09a7
241#define PCI_DEVICE_ID_INTEL_PMT_TGL	0x9a0d
242static const struct pci_device_id pmt_pci_ids[] = {
243	{ PCI_DEVICE_DATA(INTEL, PMT_ADL, &tgl_info) },
244	{ PCI_DEVICE_DATA(INTEL, PMT_DG1, &dg1_info) },
245	{ PCI_DEVICE_DATA(INTEL, PMT_OOBMSM, NULL) },
246	{ PCI_DEVICE_DATA(INTEL, PMT_TGL, &tgl_info) },
247	{ }
248};
249MODULE_DEVICE_TABLE(pci, pmt_pci_ids);
250
251static struct pci_driver pmt_pci_driver = {
252	.name = "intel-pmt",
253	.id_table = pmt_pci_ids,
254	.probe = pmt_pci_probe,
255	.remove = pmt_pci_remove,
256};
257module_pci_driver(pmt_pci_driver);
258
259MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
260MODULE_DESCRIPTION("Intel Platform Monitoring Technology PMT driver");
261MODULE_LICENSE("GPL v2");