summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/intel/pmc/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/intel/pmc/core.c')
-rw-r--r--drivers/platform/x86/intel/pmc/core.c277
1 files changed, 214 insertions, 63 deletions
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 022afb97d531..8f9c036809c7 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -20,6 +20,7 @@
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/suspend.h>
+#include <linux/units.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
@@ -27,6 +28,7 @@
#include <asm/tsc.h>
#include "core.h"
+#include "../pmt/telemetry.h"
/* Maximum number of modes supported by platfoms that has low power mode capability */
const char *pmc_lpm_modes[] = {
@@ -206,6 +208,20 @@ static int pmc_core_dev_state_get(void *data, u64 *val)
DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n");
+static int pmc_core_pson_residency_get(void *data, u64 *val)
+{
+ struct pmc *pmc = data;
+ const struct pmc_reg_map *map = pmc->map;
+ u32 value;
+
+ value = pmc_core_reg_read(pmc, map->pson_residency_offset);
+ *val = (u64)value * map->pson_residency_counter_step;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_pson_residency, pmc_core_pson_residency_get, NULL, "%llu\n");
+
static int pmc_core_check_read_lock_bit(struct pmc *pmc)
{
u32 value;
@@ -731,7 +747,7 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs);
-static void pmc_core_substate_req_header_show(struct seq_file *s)
+static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index)
{
struct pmc_dev *pmcdev = s->private;
int i, mode;
@@ -746,71 +762,125 @@ static void pmc_core_substate_req_header_show(struct seq_file *s)
static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
- const struct pmc_bit_map **maps = pmc->map->lpm_sts;
- const struct pmc_bit_map *map;
- const int num_maps = pmc->map->lpm_num_maps;
- u32 sts_offset = pmc->map->lpm_status_offset;
- u32 *lpm_req_regs = pmc->lpm_req_regs;
- int mp;
+ u32 sts_offset;
+ u32 *lpm_req_regs;
+ int num_maps, mp, pmc_index;
+
+ for (pmc_index = 0; pmc_index < ARRAY_SIZE(pmcdev->pmcs); ++pmc_index) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_index];
+ const struct pmc_bit_map **maps;
- /* Display the header */
- pmc_core_substate_req_header_show(s);
+ if (!pmc)
+ continue;
- /* Loop over maps */
- for (mp = 0; mp < num_maps; mp++) {
- u32 req_mask = 0;
- u32 lpm_status;
- int mode, idx, i, len = 32;
+ maps = pmc->map->lpm_sts;
+ num_maps = pmc->map->lpm_num_maps;
+ sts_offset = pmc->map->lpm_status_offset;
+ lpm_req_regs = pmc->lpm_req_regs;
/*
- * Capture the requirements and create a mask so that we only
- * show an element if it's required for at least one of the
- * enabled low power modes
+ * When there are multiple PMCs, though the PMC may exist, the
+ * requirement register discovery could have failed so check
+ * before accessing.
*/
- pmc_for_each_mode(idx, mode, pmcdev)
- req_mask |= lpm_req_regs[mp + (mode * num_maps)];
-
- /* Get the last latched status for this map */
- lpm_status = pmc_core_reg_read(pmc, sts_offset + (mp * 4));
-
- /* Loop over elements in this map */
- map = maps[mp];
- for (i = 0; map[i].name && i < len; i++) {
- u32 bit_mask = map[i].bit_mask;
-
- if (!(bit_mask & req_mask))
- /*
- * Not required for any enabled states
- * so don't display
- */
- continue;
-
- /* Display the element name in the first column */
- seq_printf(s, "%30s |", map[i].name);
-
- /* Loop over the enabled states and display if required */
- pmc_for_each_mode(idx, mode, pmcdev) {
- if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask)
- seq_printf(s, " %9s |",
- "Required");
- else
- seq_printf(s, " %9s |", " ");
+ if (!lpm_req_regs)
+ continue;
+
+ /* Display the header */
+ pmc_core_substate_req_header_show(s, pmc_index);
+
+ /* Loop over maps */
+ for (mp = 0; mp < num_maps; mp++) {
+ u32 req_mask = 0;
+ u32 lpm_status;
+ const struct pmc_bit_map *map;
+ int mode, idx, i, len = 32;
+
+ /*
+ * Capture the requirements and create a mask so that we only
+ * show an element if it's required for at least one of the
+ * enabled low power modes
+ */
+ pmc_for_each_mode(idx, mode, pmcdev)
+ req_mask |= lpm_req_regs[mp + (mode * num_maps)];
+
+ /* Get the last latched status for this map */
+ lpm_status = pmc_core_reg_read(pmc, sts_offset + (mp * 4));
+
+ /* Loop over elements in this map */
+ map = maps[mp];
+ for (i = 0; map[i].name && i < len; i++) {
+ u32 bit_mask = map[i].bit_mask;
+
+ if (!(bit_mask & req_mask)) {
+ /*
+ * Not required for any enabled states
+ * so don't display
+ */
+ continue;
+ }
+
+ /* Display the element name in the first column */
+ seq_printf(s, "pmc%d: %26s |", pmc_index, map[i].name);
+
+ /* Loop over the enabled states and display if required */
+ pmc_for_each_mode(idx, mode, pmcdev) {
+ bool required = lpm_req_regs[mp + (mode * num_maps)] &
+ bit_mask;
+ seq_printf(s, " %9s |", required ? "Required" : " ");
+ }
+
+ /* In Status column, show the last captured state of this agent */
+ seq_printf(s, " %9s |", lpm_status & bit_mask ? "Yes" : " ");
+
+ seq_puts(s, "\n");
}
+ }
+ }
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
- /* In Status column, show the last captured state of this agent */
- if (lpm_status & bit_mask)
- seq_printf(s, " %9s |", "Yes");
- else
- seq_printf(s, " %9s |", " ");
+static unsigned int pmc_core_get_crystal_freq(void)
+{
+ unsigned int eax_denominator, ebx_numerator, ecx_hz, edx;
- seq_puts(s, "\n");
- }
+ if (boot_cpu_data.cpuid_level < 0x15)
+ return 0;
+
+ eax_denominator = ebx_numerator = ecx_hz = edx = 0;
+
+ /* CPUID 15H TSC/Crystal ratio, plus optionally Crystal Hz */
+ cpuid(0x15, &eax_denominator, &ebx_numerator, &ecx_hz, &edx);
+
+ if (ebx_numerator == 0 || eax_denominator == 0)
+ return 0;
+
+ return ecx_hz;
+}
+
+static int pmc_core_die_c6_us_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ u64 die_c6_res, count;
+ int ret;
+
+ if (!pmcdev->crystal_freq) {
+ dev_warn_once(&pmcdev->pdev->dev, "Crystal frequency unavailable\n");
+ return -ENXIO;
}
+ ret = pmt_telem_read(pmcdev->punit_ep, pmcdev->die_c6_offset,
+ &count, 1);
+ if (ret)
+ return ret;
+
+ die_c6_res = div64_u64(count * HZ_PER_MHZ, pmcdev->crystal_freq);
+ seq_printf(s, "%llu\n", die_c6_res);
+
return 0;
}
-DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
+DEFINE_SHOW_ATTRIBUTE(pmc_core_die_c6_us);
static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
{
@@ -969,9 +1039,8 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
return true;
}
-static void pmc_core_get_low_power_modes(struct platform_device *pdev)
+void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
{
- struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
u8 mode_order[LPM_MAX_NUM_MODES];
@@ -1003,7 +1072,8 @@ static void pmc_core_get_low_power_modes(struct platform_device *pdev)
for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++)
pri_order[mode_order[mode]] = mode;
else
- dev_warn(&pdev->dev, "Assuming a default substate order for this platform\n");
+ dev_warn(&pmcdev->pdev->dev,
+ "Assuming a default substate order for this platform\n");
/*
* Loop through all modes from lowest to highest priority,
@@ -1039,6 +1109,69 @@ int get_primary_reg_base(struct pmc *pmc)
return 0;
}
+void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid)
+{
+ struct telem_endpoint *ep;
+ struct pci_dev *pcidev;
+
+ pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(10, 0));
+ if (!pcidev) {
+ dev_err(&pmcdev->pdev->dev, "PUNIT PMT device not found.");
+ return;
+ }
+
+ ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0);
+ pci_dev_put(pcidev);
+ if (IS_ERR(ep)) {
+ dev_err(&pmcdev->pdev->dev,
+ "pmc_core: couldn't get DMU telem endpoint %ld",
+ PTR_ERR(ep));
+ return;
+ }
+
+ pmcdev->punit_ep = ep;
+
+ pmcdev->has_die_c6 = true;
+ pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
+}
+
+void pmc_core_set_device_d3(unsigned int device)
+{
+ struct pci_dev *pcidev;
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
+ if (pcidev) {
+ if (!device_trylock(&pcidev->dev)) {
+ pci_dev_put(pcidev);
+ return;
+ }
+ if (!pcidev->dev.driver) {
+ dev_info(&pcidev->dev, "Setting to D3hot\n");
+ pci_set_power_state(pcidev, PCI_D3hot);
+ }
+ device_unlock(&pcidev->dev);
+ pci_dev_put(pcidev);
+ }
+}
+
+static bool pmc_core_is_pson_residency_enabled(struct pmc_dev *pmcdev)
+{
+ struct platform_device *pdev = pmcdev->pdev;
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ u8 val;
+
+ if (!adev)
+ return false;
+
+ if (fwnode_property_read_u8(acpi_fwnode_handle(adev),
+ "intel-cec-pson-switching-enabled-in-s0",
+ &val))
+ return false;
+
+ return val == 1;
+}
+
+
static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
{
debugfs_remove_recursive(pmcdev->dbgfs_dir);
@@ -1108,6 +1241,17 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_substate_req_regs_fops);
}
+
+ if (primary_pmc->map->pson_residency_offset && pmc_core_is_pson_residency_enabled(pmcdev)) {
+ debugfs_create_file("pson_residency_usec", 0444,
+ pmcdev->dbgfs_dir, primary_pmc, &pmc_core_pson_residency);
+ }
+
+ if (pmcdev->has_die_c6) {
+ debugfs_create_file("die_c6_us_show", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_die_c6_us_fops);
+ }
}
static const struct x86_cpu_id intel_pmc_core_ids[] = {
@@ -1120,18 +1264,20 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, icl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, cnp_core_init),
X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, cnp_core_init),
- X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, tgl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, tgl_l_core_init),
X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, tgl_core_init),
- X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, tgl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, tgl_l_core_init),
X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, icl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, tgl_core_init),
- X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, tgl_core_init),
- X86_MATCH_INTEL_FAM6_MODEL(ATOM_GRACEMONT, tgl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, tgl_l_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_GRACEMONT, tgl_l_core_init),
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, adl_core_init),
- X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, tgl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, tgl_l_core_init),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, adl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, adl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE_L, mtl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(ARROWLAKE, arl_core_init),
+ X86_MATCH_INTEL_FAM6_MODEL(LUNARLAKE_M, lnl_core_init),
{}
};
@@ -1202,6 +1348,10 @@ static void pmc_core_clean_structure(struct platform_device *pdev)
pci_dev_put(pmcdev->ssram_pcidev);
pci_disable_device(pmcdev->ssram_pcidev);
}
+
+ if (pmcdev->punit_ep)
+ pmt_telem_unregister_endpoint(pmcdev->punit_ep);
+
platform_set_drvdata(pdev, NULL);
mutex_destroy(&pmcdev->lock);
}
@@ -1222,6 +1372,8 @@ static int pmc_core_probe(struct platform_device *pdev)
if (!pmcdev)
return -ENOMEM;
+ pmcdev->crystal_freq = pmc_core_get_crystal_freq();
+
platform_set_drvdata(pdev, pmcdev);
pmcdev->pdev = pdev;
@@ -1253,7 +1405,6 @@ static int pmc_core_probe(struct platform_device *pdev)
}
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(primary_pmc);
- pmc_core_get_low_power_modes(pdev);
pmc_core_do_dmi_quirks(primary_pmc);
pmc_core_dbgfs_register(pmcdev);