linux-stable/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
Srinivas Pandruvada 5ba206213a thermal: intel: int340x: Add DLVR support for Lunar Lake
Add support for DLVR (Digital Linear Voltage Regulator) for Lunar Lake.
There are no new sysfs attributes or difference in operation compared
to prior generations.

MMIO offset and bit positions are changed compared to Meteor Lake
processors. Also for two attributes dlvr_frequency_mhz and
dlvr_frequency_select, the value presented or accepted by the firmware
is not raw frequency value but an index.
For example:
RFI_FREQ_SELECT and RFI_FREQ
	: 0 DLVR freq point 2227.2 MHz
	: 1 DLVR freq point 2140 MHz

Hence create a mapping table for Lunar Lake to map user space values
to the firmware accepted values.

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Link: https://patch.msgid.link/20240619124600.491168-4-srinivas.pandruvada@linux.intel.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-06-21 15:20:28 +02:00

489 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* processor thermal device RFIM control
* Copyright (c) 2020, Intel Corporation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "processor_thermal_device.h"
MODULE_IMPORT_NS(INT340X_THERMAL);
struct mmio_reg {
int read_only;
u32 offset;
int bits;
u16 mask;
u16 shift;
};
struct mapping_table {
const char *attr_name;
const u32 value;
const char *mapped_str;
};
/* These will represent sysfs attribute names */
static const char * const fivr_strings[] = {
"vco_ref_code_lo",
"vco_ref_code_hi",
"spread_spectrum_pct",
"spread_spectrum_clk_enable",
"rfi_vco_ref_code",
"fivr_fffc_rev",
NULL
};
static const struct mmio_reg tgl_fivr_mmio_regs[] = {
{ 0, 0x5A18, 3, 0x7, 11}, /* vco_ref_code_lo */
{ 0, 0x5A18, 8, 0xFF, 16}, /* vco_ref_code_hi */
{ 0, 0x5A08, 8, 0xFF, 0}, /* spread_spectrum_pct */
{ 0, 0x5A08, 1, 0x1, 8}, /* spread_spectrum_clk_enable */
{ 1, 0x5A10, 12, 0xFFF, 0}, /* rfi_vco_ref_code */
{ 1, 0x5A14, 2, 0x3, 1}, /* fivr_fffc_rev */
};
static const char * const dlvr_strings[] = {
"dlvr_spread_spectrum_pct",
"dlvr_control_mode",
"dlvr_control_lock",
"dlvr_rfim_enable",
"dlvr_freq_select",
"dlvr_hardware_rev",
"dlvr_freq_mhz",
"dlvr_pll_busy",
NULL
};
static const struct mmio_reg dlvr_mmio_regs[] = {
{ 0, 0x15A08, 5, 0x1F, 0}, /* dlvr_spread_spectrum_pct */
{ 0, 0x15A08, 1, 0x1, 5}, /* dlvr_control_mode */
{ 0, 0x15A08, 1, 0x1, 6}, /* dlvr_control_lock */
{ 0, 0x15A08, 1, 0x1, 7}, /* dlvr_rfim_enable */
{ 0, 0x15A08, 12, 0xFFF, 8}, /* dlvr_freq_select */
{ 1, 0x15A10, 2, 0x3, 30}, /* dlvr_hardware_rev */
{ 1, 0x15A10, 16, 0xFFFF, 0}, /* dlvr_freq_mhz */
{ 1, 0x15A10, 1, 0x1, 16}, /* dlvr_pll_busy */
};
static const struct mmio_reg lnl_dlvr_mmio_regs[] = {
{ 0, 0x5A08, 5, 0x1F, 0}, /* dlvr_spread_spectrum_pct */
{ 0, 0x5A08, 1, 0x1, 5}, /* dlvr_control_mode */
{ 0, 0x5A08, 1, 0x1, 6}, /* dlvr_control_lock */
{ 0, 0x5A08, 1, 0x1, 7}, /* dlvr_rfim_enable */
{ 0, 0x5A08, 2, 0x3, 8}, /* dlvr_freq_select */
{ 1, 0x5A10, 2, 0x3, 30}, /* dlvr_hardware_rev */
{ 1, 0x5A10, 2, 0x3, 0}, /* dlvr_freq_mhz */
{ 1, 0x5A10, 1, 0x1, 23}, /* dlvr_pll_busy */
};
static const struct mapping_table lnl_dlvr_mapping[] = {
{"dlvr_freq_select", 0, "2227.2"},
{"dlvr_freq_select", 1, "2140"},
{"dlvr_freq_mhz", 0, "2227.2"},
{"dlvr_freq_mhz", 1, "2140"},
{NULL, 0, NULL},
};
static int match_mapping_table(const struct mapping_table *table, const char *attr_name,
bool match_int_value, const u32 value, const char *value_str,
char **result_str, u32 *result_int)
{
bool attr_matched = false;
int i = 0;
if (!table)
return -EOPNOTSUPP;
while (table[i].attr_name) {
if (strncmp(table[i].attr_name, attr_name, strlen(attr_name)))
goto match_next;
attr_matched = true;
if (match_int_value) {
if (table[i].value != value)
goto match_next;
*result_str = (char *)table[i].mapped_str;
return 0;
}
if (strncmp(table[i].mapped_str, value_str, strlen(table[i].mapped_str)))
goto match_next;
*result_int = table[i].value;
return 0;
match_next:
i++;
}
/* If attribute name is matched, then the user space value is invalid */
if (attr_matched)
return -EINVAL;
return -EOPNOTSUPP;
}
static int get_mapped_string(const struct mapping_table *table, const char *attr_name,
u32 value, char **result)
{
return match_mapping_table(table, attr_name, true, value, NULL, result, NULL);
}
static int get_mapped_value(const struct mapping_table *table, const char *attr_name,
const char *value, unsigned int *result)
{
return match_mapping_table(table, attr_name, false, 0, value, NULL, result);
}
/* These will represent sysfs attribute names */
static const char * const dvfs_strings[] = {
"rfi_restriction_run_busy",
"rfi_restriction_err_code",
"rfi_restriction_data_rate",
"rfi_restriction_data_rate_base",
"ddr_data_rate_point_0",
"ddr_data_rate_point_1",
"ddr_data_rate_point_2",
"ddr_data_rate_point_3",
"rfi_disable",
NULL
};
static const struct mmio_reg adl_dvfs_mmio_regs[] = {
{ 0, 0x5A38, 1, 0x1, 31}, /* rfi_restriction_run_busy */
{ 0, 0x5A38, 7, 0x7F, 24}, /* rfi_restriction_err_code */
{ 0, 0x5A38, 8, 0xFF, 16}, /* rfi_restriction_data_rate */
{ 0, 0x5A38, 16, 0xFFFF, 0}, /* rfi_restriction_data_rate_base */
{ 0, 0x5A30, 10, 0x3FF, 0}, /* ddr_data_rate_point_0 */
{ 0, 0x5A30, 10, 0x3FF, 10}, /* ddr_data_rate_point_1 */
{ 0, 0x5A30, 10, 0x3FF, 20}, /* ddr_data_rate_point_2 */
{ 0, 0x5A30, 10, 0x3FF, 30}, /* ddr_data_rate_point_3 */
{ 0, 0x5A40, 1, 0x1, 0}, /* rfi_disable */
};
#define RFIM_SHOW(suffix, table)\
static ssize_t suffix##_show(struct device *dev,\
struct device_attribute *attr,\
char *buf)\
{\
const struct mapping_table *mapping = NULL;\
struct proc_thermal_device *proc_priv;\
struct pci_dev *pdev = to_pci_dev(dev);\
const struct mmio_reg *mmio_regs;\
const char **match_strs;\
int ret, err;\
u32 reg_val;\
char *str;\
\
proc_priv = pci_get_drvdata(pdev);\
if (table == 1) {\
match_strs = (const char **)dvfs_strings;\
mmio_regs = adl_dvfs_mmio_regs;\
} else if (table == 2) { \
match_strs = (const char **)dlvr_strings;\
if (pdev->device == PCI_DEVICE_ID_INTEL_LNLM_THERMAL) {\
mmio_regs = lnl_dlvr_mmio_regs;\
mapping = lnl_dlvr_mapping;\
} else {\
mmio_regs = dlvr_mmio_regs;\
} \
} else {\
match_strs = (const char **)fivr_strings;\
mmio_regs = tgl_fivr_mmio_regs;\
} \
ret = match_string(match_strs, -1, attr->attr.name);\
if (ret < 0)\
return ret;\
reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask;\
err = get_mapped_string(mapping, attr->attr.name, ret, &str);\
if (!err)\
return sprintf(buf, "%s\n", str);\
if (err == -EOPNOTSUPP)\
return sprintf(buf, "%u\n", ret);\
return err;\
}
#define RFIM_STORE(suffix, table)\
static ssize_t suffix##_store(struct device *dev,\
struct device_attribute *attr,\
const char *buf, size_t count)\
{\
const struct mapping_table *mapping = NULL;\
struct proc_thermal_device *proc_priv;\
struct pci_dev *pdev = to_pci_dev(dev);\
unsigned int input;\
const char **match_strs;\
const struct mmio_reg *mmio_regs;\
int ret, err;\
u32 reg_val;\
u32 mask;\
\
proc_priv = pci_get_drvdata(pdev);\
if (table == 1) {\
match_strs = (const char **)dvfs_strings;\
mmio_regs = adl_dvfs_mmio_regs;\
} else if (table == 2) { \
match_strs = (const char **)dlvr_strings;\
if (pdev->device == PCI_DEVICE_ID_INTEL_LNLM_THERMAL) {\
mmio_regs = lnl_dlvr_mmio_regs;\
mapping = lnl_dlvr_mapping;\
} else {\
mmio_regs = dlvr_mmio_regs;\
} \
} else {\
match_strs = (const char **)fivr_strings;\
mmio_regs = tgl_fivr_mmio_regs;\
} \
\
ret = match_string(match_strs, -1, attr->attr.name);\
if (ret < 0)\
return ret;\
if (mmio_regs[ret].read_only)\
return -EPERM;\
err = get_mapped_value(mapping, attr->attr.name, buf, &input);\
if (err == -EINVAL)\
return err;\
if (err == -EOPNOTSUPP) {\
err = kstrtouint(buf, 10, &input);\
if (err)\
return err;\
} \
mask = GENMASK(mmio_regs[ret].shift + mmio_regs[ret].bits - 1, mmio_regs[ret].shift);\
reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
reg_val &= ~mask;\
reg_val |= (input << mmio_regs[ret].shift);\
writel(reg_val, (void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
return count;\
}
RFIM_SHOW(vco_ref_code_lo, 0)
RFIM_SHOW(vco_ref_code_hi, 0)
RFIM_SHOW(spread_spectrum_pct, 0)
RFIM_SHOW(spread_spectrum_clk_enable, 0)
RFIM_SHOW(rfi_vco_ref_code, 0)
RFIM_SHOW(fivr_fffc_rev, 0)
RFIM_STORE(vco_ref_code_lo, 0)
RFIM_STORE(vco_ref_code_hi, 0)
RFIM_STORE(spread_spectrum_pct, 0)
RFIM_STORE(spread_spectrum_clk_enable, 0)
RFIM_STORE(rfi_vco_ref_code, 0)
RFIM_STORE(fivr_fffc_rev, 0)
RFIM_SHOW(dlvr_spread_spectrum_pct, 2)
RFIM_SHOW(dlvr_control_mode, 2)
RFIM_SHOW(dlvr_control_lock, 2)
RFIM_SHOW(dlvr_hardware_rev, 2)
RFIM_SHOW(dlvr_freq_mhz, 2)
RFIM_SHOW(dlvr_pll_busy, 2)
RFIM_SHOW(dlvr_freq_select, 2)
RFIM_SHOW(dlvr_rfim_enable, 2)
RFIM_STORE(dlvr_spread_spectrum_pct, 2)
RFIM_STORE(dlvr_rfim_enable, 2)
RFIM_STORE(dlvr_freq_select, 2)
RFIM_STORE(dlvr_control_mode, 2)
RFIM_STORE(dlvr_control_lock, 2)
static DEVICE_ATTR_RW(dlvr_spread_spectrum_pct);
static DEVICE_ATTR_RW(dlvr_control_mode);
static DEVICE_ATTR_RW(dlvr_control_lock);
static DEVICE_ATTR_RW(dlvr_freq_select);
static DEVICE_ATTR_RO(dlvr_hardware_rev);
static DEVICE_ATTR_RO(dlvr_freq_mhz);
static DEVICE_ATTR_RO(dlvr_pll_busy);
static DEVICE_ATTR_RW(dlvr_rfim_enable);
static struct attribute *dlvr_attrs[] = {
&dev_attr_dlvr_spread_spectrum_pct.attr,
&dev_attr_dlvr_control_mode.attr,
&dev_attr_dlvr_control_lock.attr,
&dev_attr_dlvr_freq_select.attr,
&dev_attr_dlvr_hardware_rev.attr,
&dev_attr_dlvr_freq_mhz.attr,
&dev_attr_dlvr_pll_busy.attr,
&dev_attr_dlvr_rfim_enable.attr,
NULL
};
static const struct attribute_group dlvr_attribute_group = {
.attrs = dlvr_attrs,
.name = "dlvr"
};
static DEVICE_ATTR_RW(vco_ref_code_lo);
static DEVICE_ATTR_RW(vco_ref_code_hi);
static DEVICE_ATTR_RW(spread_spectrum_pct);
static DEVICE_ATTR_RW(spread_spectrum_clk_enable);
static DEVICE_ATTR_RW(rfi_vco_ref_code);
static DEVICE_ATTR_RW(fivr_fffc_rev);
static struct attribute *fivr_attrs[] = {
&dev_attr_vco_ref_code_lo.attr,
&dev_attr_vco_ref_code_hi.attr,
&dev_attr_spread_spectrum_pct.attr,
&dev_attr_spread_spectrum_clk_enable.attr,
&dev_attr_rfi_vco_ref_code.attr,
&dev_attr_fivr_fffc_rev.attr,
NULL
};
static const struct attribute_group fivr_attribute_group = {
.attrs = fivr_attrs,
.name = "fivr"
};
RFIM_SHOW(rfi_restriction_run_busy, 1)
RFIM_SHOW(rfi_restriction_err_code, 1)
RFIM_SHOW(rfi_restriction_data_rate, 1)
RFIM_SHOW(rfi_restriction_data_rate_base, 1)
RFIM_SHOW(ddr_data_rate_point_0, 1)
RFIM_SHOW(ddr_data_rate_point_1, 1)
RFIM_SHOW(ddr_data_rate_point_2, 1)
RFIM_SHOW(ddr_data_rate_point_3, 1)
RFIM_SHOW(rfi_disable, 1)
RFIM_STORE(rfi_restriction_run_busy, 1)
RFIM_STORE(rfi_restriction_err_code, 1)
RFIM_STORE(rfi_restriction_data_rate, 1)
RFIM_STORE(rfi_restriction_data_rate_base, 1)
RFIM_STORE(rfi_disable, 1)
static DEVICE_ATTR_RW(rfi_restriction_run_busy);
static DEVICE_ATTR_RW(rfi_restriction_err_code);
static DEVICE_ATTR_RW(rfi_restriction_data_rate);
static DEVICE_ATTR_RW(rfi_restriction_data_rate_base);
static DEVICE_ATTR_RO(ddr_data_rate_point_0);
static DEVICE_ATTR_RO(ddr_data_rate_point_1);
static DEVICE_ATTR_RO(ddr_data_rate_point_2);
static DEVICE_ATTR_RO(ddr_data_rate_point_3);
static DEVICE_ATTR_RW(rfi_disable);
static ssize_t rfi_restriction_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
u16 id = 0x0008;
u32 input;
int ret;
ret = kstrtou32(buf, 10, &input);
if (ret)
return ret;
ret = processor_thermal_send_mbox_write_cmd(to_pci_dev(dev), id, input);
if (ret)
return ret;
return count;
}
static ssize_t rfi_restriction_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
u16 id = 0x0007;
u64 resp;
int ret;
ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
if (ret)
return ret;
return sprintf(buf, "%llu\n", resp);
}
static ssize_t ddr_data_rate_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
u16 id = 0x0107;
u64 resp;
int ret;
ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
if (ret)
return ret;
return sprintf(buf, "%llu\n", resp);
}
static DEVICE_ATTR_RW(rfi_restriction);
static DEVICE_ATTR_RO(ddr_data_rate);
static struct attribute *dvfs_attrs[] = {
&dev_attr_rfi_restriction_run_busy.attr,
&dev_attr_rfi_restriction_err_code.attr,
&dev_attr_rfi_restriction_data_rate.attr,
&dev_attr_rfi_restriction_data_rate_base.attr,
&dev_attr_ddr_data_rate_point_0.attr,
&dev_attr_ddr_data_rate_point_1.attr,
&dev_attr_ddr_data_rate_point_2.attr,
&dev_attr_ddr_data_rate_point_3.attr,
&dev_attr_rfi_disable.attr,
&dev_attr_ddr_data_rate.attr,
&dev_attr_rfi_restriction.attr,
NULL
};
static const struct attribute_group dvfs_attribute_group = {
.attrs = dvfs_attrs,
.name = "dvfs"
};
int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
{
int ret;
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
ret = sysfs_create_group(&pdev->dev.kobj, &fivr_attribute_group);
if (ret)
return ret;
}
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) {
ret = sysfs_create_group(&pdev->dev.kobj, &dlvr_attribute_group);
if (ret)
return ret;
}
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) {
ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group);
if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
return ret;
}
if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) {
sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(proc_thermal_rfim_add);
void proc_thermal_rfim_remove(struct pci_dev *pdev)
{
struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR)
sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group);
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
sysfs_remove_group(&pdev->dev.kobj, &dvfs_attribute_group);
}
EXPORT_SYMBOL_GPL(proc_thermal_rfim_remove);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Processor Thermal RFIM Interface");