blob: ee59096d9258a975581026298c8a1ba82ced6696 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2020 Intel Corporation
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/mfd/intel-peci-client.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include "peci-hwmon.h"
enum PECI_DIMMPOWER_SENSOR_TYPES {
PECI_DIMMPOWER_SENSOR_TYPE_POWER = 0,
PECI_DIMMPOWER_SENSOR_TYPE_ENERGY,
PECI_DIMMPOWER_SENSOR_TYPES_COUNT,
};
#define PECI_DIMMPOWER_POWER_CHANNEL_COUNT 1 /* Supported channels number */
#define PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT 1 /* Supported channels number */
#define PECI_DIMMPOWER_POWER_SENSOR_COUNT 4 /* Supported sensors/readings number */
#define PECI_DIMMPOWER_ENERGY_SENSOR_COUNT 1 /* Supported sensors/readings number */
struct peci_dimmpower {
struct device *dev;
struct peci_client_manager *mgr;
char name[PECI_NAME_SIZE];
u32 power_config[PECI_DIMMPOWER_POWER_CHANNEL_COUNT + 1];
u32 energy_config[PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT + 1];
struct hwmon_channel_info power_info;
struct hwmon_channel_info energy_info;
const struct hwmon_channel_info *info[PECI_DIMMPOWER_SENSOR_TYPES_COUNT + 1];
struct hwmon_chip_info chip;
struct peci_sensor_data
power_sensor_data_list[PECI_DIMMPOWER_POWER_CHANNEL_COUNT]
[PECI_DIMMPOWER_POWER_SENSOR_COUNT];
struct peci_sensor_data
energy_sensor_data_list[PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT]
[PECI_DIMMPOWER_ENERGY_SENSOR_COUNT];
/* Below structs are not exposed to any sensor directly */
struct peci_sensor_data energy_cache; /* used to limit PECI communication */
struct peci_sensor_data power_sensor_prev_energy;
struct peci_sensor_data energy_sensor_prev_energy;
union peci_pkg_power_sku_unit units;
bool units_valid;
u32 dpl_time_window;
bool dpl_time_window_valid;
};
static const char *peci_dimmpower_labels[PECI_DIMMPOWER_SENSOR_TYPES_COUNT] = {
"dimm power",
"dimm energy",
};
/**
* peci_dimmpower_read_dram_power_limit - read PCS DRAM Power Limit
* @peci_mgr: PECI client manager handle
* @reg: Pointer to the variable read value is going to be put
*
* Return: 0 if succeeded, other values in case an error.
*/
static inline int
peci_dimmpower_read_dram_power_limit(struct peci_client_manager *peci_mgr,
union peci_dram_power_limit *reg)
{
return peci_pcs_read(peci_mgr, PECI_MBX_INDEX_DDR_RAPL_PL1,
PECI_PCS_PARAM_ZERO, &reg->value);
}
static int
peci_dimmpower_get_energy_counter(struct peci_dimmpower *priv,
struct peci_sensor_data *sensor_data,
ulong update_interval)
{
int ret = 0;
mutex_lock(&sensor_data->lock);
if (!peci_sensor_need_update_with_time(sensor_data,
update_interval)) {
dev_dbg(priv->dev, "skip reading dimm energy over peci\n");
goto unlock;
}
ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_ENERGY_STATUS,
PECI_PKG_ID_DIMM, &sensor_data->uvalue);
if (ret) {
dev_dbg(priv->dev, "not able to read dimm energy\n");
goto unlock;
}
peci_sensor_mark_updated(sensor_data);
dev_dbg(priv->dev,
"energy counter updated %duJ, jif %lu, HZ is %d jiffies\n",
sensor_data->uvalue, sensor_data->last_updated, HZ);
unlock:
mutex_unlock(&sensor_data->lock);
return ret;
}
static int
peci_dimmpower_get_avg_power(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
int ret = 0;
mutex_lock(&sensor_data->lock);
if (!peci_sensor_need_update_with_time(sensor_data,
sensor_conf->update_interval)) {
dev_dbg(priv->dev, "skip reading peci, average power %dmW jif %lu\n",
sensor_data->value, jiffies);
goto unlock;
}
ret = peci_dimmpower_get_energy_counter(priv, &priv->energy_cache,
sensor_conf->update_interval);
if (ret) {
dev_dbg(priv->dev, "cannot update energy counter\n");
goto unlock;
}
ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
if (ret) {
dev_dbg(priv->dev, "not able to read units\n");
goto unlock;
}
ret = peci_pcs_calc_pwr_from_eng(priv->dev,
&priv->power_sensor_prev_energy,
&priv->energy_cache,
priv->units.bits.eng_unit,
&sensor_data->value);
if (ret) {
dev_dbg(priv->dev, "power calculation failed\n");
goto unlock;
}
peci_sensor_mark_updated_with_time(sensor_data, priv->energy_cache.last_updated);
dev_dbg(priv->dev, "average power %dmW, jif %lu, HZ is %d jiffies\n",
sensor_data->value, sensor_data->last_updated, HZ);
unlock:
mutex_unlock(&sensor_data->lock);
return ret;
}
static int
peci_dimmpower_get_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
union peci_dram_power_limit power_limit;
int ret = 0;
mutex_lock(&sensor_data->lock);
if (!peci_sensor_need_update_with_time(sensor_data,
sensor_conf->update_interval)) {
dev_dbg(priv->dev, "skip reading peci, power limit %dmW\n",
sensor_data->value);
goto unlock;
}
ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
if (ret) {
dev_dbg(priv->dev, "not able to read units\n");
goto unlock;
}
ret = peci_dimmpower_read_dram_power_limit(priv->mgr, &power_limit);
if (ret) {
dev_dbg(priv->dev, "not able to read power limit\n");
goto unlock;
}
peci_sensor_mark_updated(sensor_data);
sensor_data->value = peci_pcs_xn_to_munits(power_limit.bits.pp_pwr_lim,
priv->units.bits.pwr_unit);
dev_dbg(priv->dev, "raw power limit %u, unit %u, power limit %d\n",
power_limit.bits.pp_pwr_lim, priv->units.bits.pwr_unit,
sensor_data->value);
unlock:
mutex_unlock(&sensor_data->lock);
return ret;
}
static int
peci_dimmpower_set_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data,
s32 val)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
union peci_dram_power_limit power_limit;
int ret;
ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
if (ret) {
dev_dbg(priv->dev, "not able to read units\n");
return ret;
}
ret = peci_dimmpower_read_dram_power_limit(priv->mgr, &power_limit);
if (ret) {
dev_dbg(priv->dev, "not able to read power limit\n");
return ret;
}
/* Calculate DPL time window if needed */
if (!priv->dpl_time_window_valid) {
priv->dpl_time_window =
peci_pcs_calc_plxy_time_window(peci_pcs_munits_to_xn(
PECI_PCS_PPL1_TIME_WINDOW,
priv->units.bits.tim_unit));
priv->dpl_time_window_valid = true;
}
/* Enable or disable power limitation */
if (val > 0) {
power_limit.bits.pp_pwr_lim =
peci_pcs_munits_to_xn(val, priv->units.bits.pwr_unit);
power_limit.bits.pwr_lim_ctrl_en = 1u;
power_limit.bits.ctrl_time_win = priv->dpl_time_window;
} else {
power_limit.bits.pp_pwr_lim = 0u;
power_limit.bits.pwr_lim_ctrl_en = 0u;
power_limit.bits.ctrl_time_win = 0u;
}
ret = peci_pcs_write(priv->mgr, PECI_MBX_INDEX_DDR_RAPL_PL1,
PECI_PCS_PARAM_ZERO, power_limit.value);
if (ret) {
dev_dbg(priv->dev, "not able to write power limit\n");
return ret;
}
dev_dbg(priv->dev, "power limit %d, unit %u, raw power limit %u,\n",
val, priv->units.bits.pwr_unit, power_limit.bits.pp_pwr_lim);
return ret;
}
static int
peci_dimmpower_read_max_power(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
union peci_dram_power_info_low power_info;
int ret = 0;
mutex_lock(&sensor_data->lock);
if (!peci_sensor_need_update_with_time(sensor_data,
sensor_conf->update_interval)) {
dev_dbg(priv->dev, "skip reading peci, max power %dmW\n",
sensor_data->value);
goto unlock;
}
ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
if (ret) {
dev_dbg(priv->dev, "not able to read units\n");
goto unlock;
}
ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_DDR_PWR_INFO_LOW,
PECI_PCS_PARAM_ZERO, &power_info.value);
if (ret) {
dev_dbg(priv->dev, "not able to read power info\n");
goto unlock;
}
peci_sensor_mark_updated(sensor_data);
sensor_data->value = peci_pcs_xn_to_munits(power_info.bits.tdp,
priv->units.bits.pwr_unit);
dev_dbg(priv->dev, "raw max power %u, unit %u, max power %dmW\n",
power_info.bits.tdp, priv->units.bits.pwr_unit,
sensor_data->value);
unlock:
mutex_unlock(&sensor_data->lock);
return ret;
}
static int
peci_dimmpower_read_min_power(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
/* DRAM_POWER_INFO.DRAM_MIN_PWR is no more supported in CPU starting from
* SPR. So BIOS doesn't update this. That's why there is still default
* value (15W) which doesn't make sense. There should be a case when
* MAX_PWR/TDP is smaller than 15W.
* 0 seems to be a reasonable value for that parameter.
*/
sensor_data->value = 0;
dev_dbg(priv->dev, "min power %dmW\n", sensor_data->value);
return 0;
}
static int
peci_dimmpower_read_energy(void *ctx, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data)
{
struct peci_dimmpower *priv = (struct peci_dimmpower *)ctx;
int ret = 0;
mutex_lock(&sensor_data->lock);
if (!peci_sensor_need_update_with_time(sensor_data,
sensor_conf->update_interval)) {
dev_dbg(priv->dev,
"skip generating new energy value %duJ jif %lu\n",
sensor_data->uvalue, jiffies);
goto unlock;
}
ret = peci_dimmpower_get_energy_counter(priv, &priv->energy_cache,
sensor_conf->update_interval);
if (ret) {
dev_dbg(priv->dev, "cannot update energy counter\n");
goto unlock;
}
ret = peci_pcs_get_units(priv->mgr, &priv->units, &priv->units_valid);
if (ret) {
dev_dbg(priv->dev, "not able to read units\n");
goto unlock;
}
ret = peci_pcs_calc_acc_eng(priv->dev,
&priv->energy_sensor_prev_energy,
&priv->energy_cache,
priv->units.bits.eng_unit,
&sensor_data->uvalue);
if (ret) {
dev_dbg(priv->dev, "cumulative energy calculation failed\n");
goto unlock;
}
peci_sensor_mark_updated_with_time(sensor_data,
priv->energy_cache.last_updated);
dev_dbg(priv->dev, "energy %duJ, jif %lu, HZ is %d jiffies\n",
sensor_data->uvalue, sensor_data->last_updated, HZ);
unlock:
mutex_unlock(&sensor_data->lock);
return ret;
}
static struct peci_sensor_conf
peci_dimmpower_power_cfg[PECI_DIMMPOWER_POWER_CHANNEL_COUNT]
[PECI_DIMMPOWER_POWER_SENSOR_COUNT] = {
/* Channel 0 - Power */
{
{
.attribute = hwmon_power_average,
.config = HWMON_P_AVERAGE,
.update_interval = UPDATE_INTERVAL_100MS,
.read = peci_dimmpower_get_avg_power,
.write = NULL,
},
{
.attribute = hwmon_power_cap,
.config = HWMON_P_CAP,
.update_interval = UPDATE_INTERVAL_100MS,
.read = peci_dimmpower_get_power_limit,
.write = peci_dimmpower_set_power_limit,
},
{
.attribute = hwmon_power_cap_max,
.config = HWMON_P_CAP_MAX,
.update_interval = UPDATE_INTERVAL_10S,
.read = peci_dimmpower_read_max_power,
.write = NULL,
},
{
.attribute = hwmon_power_cap_min,
.config = HWMON_P_CAP_MIN,
.update_interval = UPDATE_INTERVAL_10S,
.read = peci_dimmpower_read_min_power,
.write = NULL,
},
},
};
static struct peci_sensor_conf
peci_dimmpower_energy_cfg[PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT]
[PECI_DIMMPOWER_ENERGY_SENSOR_COUNT] = {
/* Channel 0 - Energy */
{
{
.attribute = hwmon_energy_input,
.config = HWMON_E_INPUT,
.update_interval = UPDATE_INTERVAL_100MS,
.read = peci_dimmpower_read_energy,
.write = NULL,
},
}
};
static bool
peci_dimmpower_is_channel_valid(enum hwmon_sensor_types type,
int channel)
{
if ((type == hwmon_power && channel < PECI_DIMMPOWER_POWER_CHANNEL_COUNT) ||
(type == hwmon_energy && channel < PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT))
return true;
return false;
}
static int
peci_dimmpower_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
if (!peci_dimmpower_is_channel_valid(type, channel))
return -EOPNOTSUPP;
switch (attr) {
case hwmon_power_label:
*str = peci_dimmpower_labels[PECI_DIMMPOWER_SENSOR_TYPE_POWER];
break;
case hwmon_energy_label:
*str = peci_dimmpower_labels[PECI_DIMMPOWER_SENSOR_TYPE_ENERGY];
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int
peci_dimmpower_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct peci_dimmpower *priv = dev_get_drvdata(dev);
struct peci_sensor_conf *sensor_conf;
struct peci_sensor_data *sensor_data;
int ret;
if (!priv || !val)
return -EINVAL;
if (!peci_dimmpower_is_channel_valid(type, channel))
return -EOPNOTSUPP;
switch (type) {
case hwmon_power:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_power_cfg[channel],
&sensor_conf,
priv->power_sensor_data_list[channel],
&sensor_data,
ARRAY_SIZE(peci_dimmpower_power_cfg[channel]));
break;
case hwmon_energy:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_energy_cfg[channel],
&sensor_conf,
priv->energy_sensor_data_list[channel],
&sensor_data,
ARRAY_SIZE(peci_dimmpower_energy_cfg[channel]));
break;
default:
ret = -EOPNOTSUPP;
}
if (ret)
return ret;
if (sensor_conf->read) {
ret = sensor_conf->read(priv, sensor_conf, sensor_data);
if (!ret)
*val = (long)sensor_data->value;
} else {
ret = -EOPNOTSUPP;
}
return ret;
}
static int
peci_dimmpower_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct peci_dimmpower *priv = dev_get_drvdata(dev);
struct peci_sensor_conf *sensor_conf;
struct peci_sensor_data *sensor_data;
int ret;
if (!priv)
return -EINVAL;
if (!peci_dimmpower_is_channel_valid(type, channel))
return -EOPNOTSUPP;
switch (type) {
case hwmon_power:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_power_cfg[channel],
&sensor_conf,
priv->power_sensor_data_list[channel],
&sensor_data,
ARRAY_SIZE(peci_dimmpower_power_cfg[channel]));
break;
case hwmon_energy:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_energy_cfg[channel],
&sensor_conf,
priv->energy_sensor_data_list[channel],
&sensor_data,
ARRAY_SIZE(peci_dimmpower_energy_cfg[channel]));
break;
default:
ret = -EOPNOTSUPP;
}
if (ret)
return ret;
if (sensor_conf->write) {
ret = sensor_conf->write(priv, sensor_conf, sensor_data,
(s32)val);
} else {
ret = -EOPNOTSUPP;
}
return ret;
}
static umode_t
peci_dimmpower_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
struct peci_sensor_conf *sensor_conf;
umode_t mode = 0;
int ret;
if (!peci_dimmpower_is_channel_valid(type, channel))
return mode;
if (attr == hwmon_power_label || attr == hwmon_energy_label)
return 0444;
switch (type) {
case hwmon_power:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_power_cfg[channel],
&sensor_conf, NULL, NULL,
ARRAY_SIZE(peci_dimmpower_power_cfg[channel]));
break;
case hwmon_energy:
ret = peci_sensor_get_ctx(attr, peci_dimmpower_energy_cfg[channel],
&sensor_conf, NULL, NULL,
ARRAY_SIZE(peci_dimmpower_energy_cfg[channel]));
break;
default:
return mode;
}
if (!ret) {
if (sensor_conf->read)
mode |= 0444;
if (sensor_conf->write)
mode |= 0200;
}
return mode;
}
static const struct hwmon_ops peci_dimmpower_ops = {
.is_visible = peci_dimmpower_is_visible,
.read_string = peci_dimmpower_read_string,
.read = peci_dimmpower_read,
.write = peci_dimmpower_write,
};
static void peci_dimmpower_sensor_init(struct peci_dimmpower *priv)
{
int i, j;
mutex_init(&priv->energy_cache.lock);
for (i = 0; i < PECI_DIMMPOWER_POWER_CHANNEL_COUNT; i++) {
for (j = 0; j < PECI_DIMMPOWER_POWER_SENSOR_COUNT; j++)
mutex_init(&priv->power_sensor_data_list[i][j].lock);
}
for (i = 0; i < PECI_DIMMPOWER_ENERGY_CHANNEL_COUNT; i++) {
for (j = 0; j < PECI_DIMMPOWER_ENERGY_SENSOR_COUNT; j++)
mutex_init(&priv->energy_sensor_data_list[i][j].lock);
}
}
static int peci_dimmpower_probe(struct platform_device *pdev)
{
struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct peci_dimmpower *priv;
struct device *hwmon_dev;
u32 power_config_idx = 0;
u32 energy_config_idx = 0;
u32 cmd_mask;
cmd_mask = BIT(PECI_CMD_RD_PKG_CFG) | BIT(PECI_CMD_WR_PKG_CFG);
if ((mgr->client->adapter->cmd_mask & cmd_mask) != cmd_mask)
return -ENODEV;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(dev, priv);
priv->mgr = mgr;
priv->dev = dev;
snprintf(priv->name, PECI_NAME_SIZE, "peci_dimmpower.cpu%d.%d",
mgr->client->addr - PECI_BASE_ADDR, mgr->client->domain_id);
priv->power_config[power_config_idx] = HWMON_P_LABEL |
peci_sensor_get_config(peci_dimmpower_power_cfg[power_config_idx],
ARRAY_SIZE(peci_dimmpower_power_cfg[power_config_idx]));
priv->energy_config[energy_config_idx] = HWMON_E_LABEL |
peci_sensor_get_config(peci_dimmpower_energy_cfg[energy_config_idx],
ARRAY_SIZE(peci_dimmpower_energy_cfg[energy_config_idx]));
priv->info[PECI_DIMMPOWER_SENSOR_TYPE_POWER] = &priv->power_info;
priv->power_info.type = hwmon_power;
priv->power_info.config = priv->power_config;
priv->info[PECI_DIMMPOWER_SENSOR_TYPE_ENERGY] = &priv->energy_info;
priv->energy_info.type = hwmon_energy;
priv->energy_info.config = priv->energy_config;
priv->chip.ops = &peci_dimmpower_ops;
priv->chip.info = priv->info;
peci_dimmpower_sensor_init(priv);
hwmon_dev = devm_hwmon_device_register_with_info(priv->dev, priv->name,
priv, &priv->chip,
NULL);
if (IS_ERR(hwmon_dev))
return PTR_ERR(hwmon_dev);
dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), priv->name);
return 0;
}
static const struct platform_device_id peci_dimmpower_ids[] = {
{ .name = "peci-dimmpower", .driver_data = 0 },
{ }
};
MODULE_DEVICE_TABLE(platform, peci_dimmpower_ids);
static struct platform_driver peci_dimmpower_driver = {
.probe = peci_dimmpower_probe,
.id_table = peci_dimmpower_ids,
.driver = { .name = KBUILD_MODNAME, },
};
module_platform_driver(peci_dimmpower_driver);
MODULE_AUTHOR("Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>");
MODULE_DESCRIPTION("PECI dimmpower driver");
MODULE_LICENSE("GPL v2");