linux/drivers/mfd/intel_pmt.c
<<
>>
Prefs
   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");
 262