diff options
Diffstat (limited to 'drivers/platform/x86/intel/pmc/core.c')
-rw-r--r-- | drivers/platform/x86/intel/pmc/core.c | 128 |
1 files changed, 116 insertions, 12 deletions
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 01ae71c6df59..ecb47f8b4f83 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -11,6 +11,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/acpi_pmtmr.h> #include <linux/bitfield.h> #include <linux/debugfs.h> #include <linux/delay.h> @@ -714,6 +715,49 @@ static int pmc_core_s0ix_blocker_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(pmc_core_s0ix_blocker); +static void pmc_core_ltr_ignore_all(struct pmc_dev *pmcdev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); i++) { + struct pmc *pmc; + u32 ltr_ign; + + pmc = pmcdev->pmcs[i]; + if (!pmc) + continue; + + guard(mutex)(&pmcdev->lock); + pmc->ltr_ign = pmc_core_reg_read(pmc, pmc->map->ltr_ignore_offset); + + /* ltr_ignore_max is the max index value for LTR ignore register */ + ltr_ign = pmc->ltr_ign | GENMASK(pmc->map->ltr_ignore_max, 0); + pmc_core_reg_write(pmc, pmc->map->ltr_ignore_offset, ltr_ign); + } + + /* + * Ignoring ME during suspend is blocking platforms with ADL PCH to get to + * deeper S0ix substate. + */ + pmc_core_send_ltr_ignore(pmcdev, 6, 0); +} + +static void pmc_core_ltr_restore_all(struct pmc_dev *pmcdev) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); i++) { + struct pmc *pmc; + + pmc = pmcdev->pmcs[i]; + if (!pmc) + continue; + + guard(mutex)(&pmcdev->lock); + pmc_core_reg_write(pmc, pmc->map->ltr_ignore_offset, pmc->ltr_ign); + } +} + static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset, const int lpm_adj_x2) { @@ -728,12 +772,11 @@ static int pmc_core_substate_res_show(struct seq_file *s, void *unused) struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2; u32 offset = pmc->map->lpm_residency_offset; - unsigned int i; int mode; seq_printf(s, "%-10s %-15s\n", "Substate", "Residency"); - pmc_for_each_mode(i, mode, pmcdev) { + pmc_for_each_mode(mode, pmcdev) { seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode], adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2)); } @@ -787,20 +830,21 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs); static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index) { struct pmc_dev *pmcdev = s->private; - unsigned int i; int mode; seq_printf(s, "%30s |", "Element"); - pmc_for_each_mode(i, mode, pmcdev) + pmc_for_each_mode(mode, pmcdev) seq_printf(s, " %9s |", pmc_lpm_modes[mode]); - seq_printf(s, " %9s |\n", "Status"); + seq_printf(s, " %9s |", "Status"); + seq_printf(s, " %11s |\n", "Live Status"); } static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) { struct pmc_dev *pmcdev = s->private; u32 sts_offset; + u32 sts_offset_live; u32 *lpm_req_regs; unsigned int mp, pmc_index; int num_maps; @@ -815,6 +859,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) maps = pmc->map->lpm_sts; num_maps = pmc->map->lpm_num_maps; sts_offset = pmc->map->lpm_status_offset; + sts_offset_live = pmc->map->lpm_live_status_offset; lpm_req_regs = pmc->lpm_req_regs; /* @@ -832,20 +877,24 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) for (mp = 0; mp < num_maps; mp++) { u32 req_mask = 0; u32 lpm_status; + u32 lpm_status_live; const struct pmc_bit_map *map; - int mode, idx, i, len = 32; + int mode, 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) + pmc_for_each_mode(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)); + /* Get the runtime status for this map */ + lpm_status_live = pmc_core_reg_read(pmc, sts_offset_live + (mp * 4)); + /* Loop over elements in this map */ map = maps[mp]; for (i = 0; map[i].name && i < len; i++) { @@ -863,7 +912,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) 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) { + pmc_for_each_mode(mode, pmcdev) { bool required = lpm_req_regs[mp + (mode * num_maps)] & bit_mask; seq_printf(s, " %9s |", required ? "Required" : " "); @@ -872,6 +921,9 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) /* In Status column, show the last captured state of this agent */ seq_printf(s, " %9s |", lpm_status & bit_mask ? "Yes" : " "); + /* In Live status column, show the live state of this agent */ + seq_printf(s, " %11s |", lpm_status_live & bit_mask ? "Yes" : " "); + seq_puts(s, "\n"); } } @@ -925,7 +977,6 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) { struct pmc_dev *pmcdev = s->private; struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; - unsigned int idx; bool c10; u32 reg; int mode; @@ -939,7 +990,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) c10 = true; } - pmc_for_each_mode(idx, mode, pmcdev) { + pmc_for_each_mode(mode, pmcdev) { if ((BIT(mode) & reg) && !c10) seq_printf(s, " [%s]", pmc_lpm_modes[mode]); else @@ -960,7 +1011,6 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; bool clear = false, c10 = false; unsigned char buf[8]; - unsigned int idx; int m, mode; u32 reg; @@ -979,7 +1029,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, mode = sysfs_match_string(pmc_lpm_modes, buf); /* Check string matches enabled mode */ - pmc_for_each_mode(idx, m, pmcdev) + pmc_for_each_mode(m, pmcdev) if (mode == m) break; @@ -1208,6 +1258,38 @@ static bool pmc_core_is_pson_residency_enabled(struct pmc_dev *pmcdev) return val == 1; } +/* + * Enable or disable ACPI PM Timer + * + * This function is intended to be a callback for ACPI PM suspend/resume event. + * The ACPI PM Timer is enabled on resume only if it was enabled during suspend. + */ +static void pmc_core_acpi_pm_timer_suspend_resume(void *data, bool suspend) +{ + struct pmc_dev *pmcdev = data; + struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; + const struct pmc_reg_map *map = pmc->map; + bool enabled; + u32 reg; + + if (!map->acpi_pm_tmr_ctl_offset) + return; + + guard(mutex)(&pmcdev->lock); + + if (!suspend && !pmcdev->enable_acpi_pm_timer_on_resume) + return; + + reg = pmc_core_reg_read(pmc, map->acpi_pm_tmr_ctl_offset); + enabled = !(reg & map->acpi_pm_tmr_disable_bit); + if (suspend) + reg |= map->acpi_pm_tmr_disable_bit; + else + reg &= ~map->acpi_pm_tmr_disable_bit; + pmc_core_reg_write(pmc, map->acpi_pm_tmr_ctl_offset, reg); + + pmcdev->enable_acpi_pm_timer_on_resume = suspend && enabled; +} static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) { @@ -1404,6 +1486,7 @@ static int pmc_core_probe(struct platform_device *pdev) struct pmc_dev *pmcdev; const struct x86_cpu_id *cpu_id; int (*core_init)(struct pmc_dev *pmcdev); + const struct pmc_reg_map *map; struct pmc *primary_pmc; int ret; @@ -1462,6 +1545,11 @@ static int pmc_core_probe(struct platform_device *pdev) pm_report_max_hw_sleep(FIELD_MAX(SLP_S0_RES_COUNTER_MASK) * pmc_core_adjust_slp_s0_step(primary_pmc, 1)); + map = primary_pmc->map; + if (map->acpi_pm_tmr_ctl_offset) + acpi_pmtmr_register_suspend_resume_callback(pmc_core_acpi_pm_timer_suspend_resume, + pmcdev); + device_initialized = true; dev_info(&pdev->dev, " initialized\n"); @@ -1471,6 +1559,12 @@ static int pmc_core_probe(struct platform_device *pdev) static void pmc_core_remove(struct platform_device *pdev) { struct pmc_dev *pmcdev = platform_get_drvdata(pdev); + const struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN]; + const struct pmc_reg_map *map = pmc->map; + + if (map->acpi_pm_tmr_ctl_offset) + acpi_pmtmr_unregister_suspend_resume_callback(); + pmc_core_dbgfs_unregister(pmcdev); pmc_core_clean_structure(pdev); } @@ -1479,6 +1573,10 @@ static bool warn_on_s0ix_failures; module_param(warn_on_s0ix_failures, bool, 0644); MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures"); +static bool ltr_ignore_all_suspend = true; +module_param(ltr_ignore_all_suspend, bool, 0644); +MODULE_PARM_DESC(ltr_ignore_all_suspend, "Ignore all LTRs during suspend"); + static __maybe_unused int pmc_core_suspend(struct device *dev) { struct pmc_dev *pmcdev = dev_get_drvdata(dev); @@ -1488,6 +1586,9 @@ static __maybe_unused int pmc_core_suspend(struct device *dev) if (pmcdev->suspend) pmcdev->suspend(pmcdev); + if (ltr_ignore_all_suspend) + pmc_core_ltr_ignore_all(pmcdev); + /* Check if the syspend will actually use S0ix */ if (pm_suspend_via_firmware()) return 0; @@ -1594,6 +1695,9 @@ static __maybe_unused int pmc_core_resume(struct device *dev) { struct pmc_dev *pmcdev = dev_get_drvdata(dev); + if (ltr_ignore_all_suspend) + pmc_core_ltr_restore_all(pmcdev); + if (pmcdev->resume) return pmcdev->resume(pmcdev); |