linux/drivers/platform/x86/intel_pmc_core.c
<<
>>
Prefs
   1/*
   2 * Intel Core SoC Power Management Controller Driver
   3 *
   4 * Copyright (c) 2016, Intel Corporation.
   5 * All Rights Reserved.
   6 *
   7 * Authors: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
   8 *          Vishwanath Somayaji <vishwanath.somayaji@intel.com>
   9 *
  10 * This program is free software; you can redistribute it and/or modify it
  11 * under the terms and conditions of the GNU General Public License,
  12 * version 2, as published by the Free Software Foundation.
  13 *
  14 * This program is distributed in the hope it will be useful, but WITHOUT
  15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  16 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  17 * more details.
  18 *
  19 */
  20
  21#include <linux/debugfs.h>
  22#include <linux/device.h>
  23#include <linux/init.h>
  24#include <linux/io.h>
  25#include <linux/pci.h>
  26
  27#include <asm/cpu_device_id.h>
  28#include <asm/intel-family.h>
  29#include <asm/pmc_core.h>
  30
  31#include "intel_pmc_core.h"
  32
  33static struct pmc_dev pmc;
  34
  35static const struct pci_device_id pmc_pci_ids[] = {
  36        { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID), (kernel_ulong_t)NULL },
  37        { 0, },
  38};
  39
  40static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)
  41{
  42        return readl(pmcdev->regbase + reg_offset);
  43}
  44
  45static inline u32 pmc_core_adjust_slp_s0_step(u32 value)
  46{
  47        return value * SPT_PMC_SLP_S0_RES_COUNTER_STEP;
  48}
  49
  50/**
  51 * intel_pmc_slp_s0_counter_read() - Read SLP_S0 residency.
  52 * @data: Out param that contains current SLP_S0 count.
  53 *
  54 * This API currently supports Intel Skylake SoC and Sunrise
  55 * Point Platform Controller Hub. Future platform support
  56 * should be added for platforms that support low power modes
  57 * beyond Package C10 state.
  58 *
  59 * SLP_S0_RESIDENCY counter counts in 100 us granularity per
  60 * step hence function populates the multiplied value in out
  61 * parameter @data.
  62 *
  63 * Return: an error code or 0 on success.
  64 */
  65int intel_pmc_slp_s0_counter_read(u32 *data)
  66{
  67        struct pmc_dev *pmcdev = &pmc;
  68        u32 value;
  69
  70        if (!pmcdev->has_slp_s0_res)
  71                return -EACCES;
  72
  73        value = pmc_core_reg_read(pmcdev, SPT_PMC_SLP_S0_RES_COUNTER_OFFSET);
  74        *data = pmc_core_adjust_slp_s0_step(value);
  75
  76        return 0;
  77}
  78EXPORT_SYMBOL_GPL(intel_pmc_slp_s0_counter_read);
  79
  80static int pmc_core_dev_state_get(void *data, u64 *val)
  81{
  82        struct pmc_dev *pmcdev = data;
  83        u32 value;
  84
  85        value = pmc_core_reg_read(pmcdev, SPT_PMC_SLP_S0_RES_COUNTER_OFFSET);
  86        *val = pmc_core_adjust_slp_s0_step(value);
  87
  88        return 0;
  89}
  90
  91DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n");
  92
  93static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
  94{
  95        debugfs_remove_recursive(pmcdev->dbgfs_dir);
  96}
  97
  98static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
  99{
 100        struct dentry *dir, *file;
 101
 102        dir = debugfs_create_dir("pmc_core", NULL);
 103        if (!dir)
 104                return -ENOMEM;
 105
 106        pmcdev->dbgfs_dir = dir;
 107        file = debugfs_create_file("slp_s0_residency_usec", S_IFREG | S_IRUGO,
 108                                   dir, pmcdev, &pmc_core_dev_state);
 109
 110        if (!file) {
 111                pmc_core_dbgfs_unregister(pmcdev);
 112                return -ENODEV;
 113        }
 114
 115        return 0;
 116}
 117
 118static const struct x86_cpu_id intel_pmc_core_ids[] = {
 119        { X86_VENDOR_INTEL, 6, INTEL_FAM6_SKYLAKE_MOBILE, X86_FEATURE_MWAIT,
 120                (kernel_ulong_t)NULL},
 121        { X86_VENDOR_INTEL, 6, INTEL_FAM6_SKYLAKE_DESKTOP, X86_FEATURE_MWAIT,
 122                (kernel_ulong_t)NULL},
 123        {}
 124};
 125
 126static int pmc_core_probe(struct pci_dev *dev, const struct pci_device_id *id)
 127{
 128        struct device *ptr_dev = &dev->dev;
 129        struct pmc_dev *pmcdev = &pmc;
 130        const struct x86_cpu_id *cpu_id;
 131        int err;
 132
 133        cpu_id = x86_match_cpu(intel_pmc_core_ids);
 134        if (!cpu_id) {
 135                dev_dbg(&dev->dev, "PMC Core: cpuid mismatch.\n");
 136                return -EINVAL;
 137        }
 138
 139        err = pcim_enable_device(dev);
 140        if (err < 0) {
 141                dev_dbg(&dev->dev, "PMC Core: failed to enable Power Management Controller.\n");
 142                return err;
 143        }
 144
 145        err = pci_read_config_dword(dev,
 146                                    SPT_PMC_BASE_ADDR_OFFSET,
 147                                    &pmcdev->base_addr);
 148        if (err < 0) {
 149                dev_dbg(&dev->dev, "PMC Core: failed to read PCI config space.\n");
 150                return err;
 151        }
 152        dev_dbg(&dev->dev, "PMC Core: PWRMBASE is %#x\n", pmcdev->base_addr);
 153
 154        pmcdev->regbase = devm_ioremap_nocache(ptr_dev,
 155                                              pmcdev->base_addr,
 156                                              SPT_PMC_MMIO_REG_LEN);
 157        if (!pmcdev->regbase) {
 158                dev_dbg(&dev->dev, "PMC Core: ioremap failed.\n");
 159                return -ENOMEM;
 160        }
 161
 162        err = pmc_core_dbgfs_register(pmcdev);
 163        if (err < 0)
 164                dev_warn(&dev->dev, "PMC Core: debugfs register failed.\n");
 165
 166        pmc.has_slp_s0_res = true;
 167        return 0;
 168}
 169
 170static struct pci_driver intel_pmc_core_driver = {
 171        .name = "intel_pmc_core",
 172        .id_table = pmc_pci_ids,
 173        .probe = pmc_core_probe,
 174};
 175
 176builtin_pci_driver(intel_pmc_core_driver);
 177