blob: c6947346cf7736a3707a60c015ee73ebe5d0d22b [file] [log] [blame] [edit]
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018-2020 Intel Corporation */
#ifndef __PECI_HWMON_H
#define __PECI_HWMON_H
#include <linux/peci.h>
#include <asm/div64.h>
#define TEMP_TYPE_PECI 6 /* Sensor type 6: Intel PECI */
#define UPDATE_INTERVAL_DEFAULT HZ
#define UPDATE_INTERVAL_100MS (HZ / 10)
#define UPDATE_INTERVAL_10S (HZ * 10)
#define PECI_HWMON_LABEL_STR_LEN 10
/**
* struct peci_sensor_data - PECI sensor information
* @valid: flag to indicate the sensor value is valid
* @value: sensor value in milli units
* @last_updated: time of the last update in jiffies
*/
struct peci_sensor_data {
uint valid;
union {
s32 value;
u32 uvalue;
};
ulong last_updated;
struct mutex lock; /* protect sensor access */
};
/**
* peci_sensor_need_update - check whether sensor update is needed or not
* @sensor: pointer to sensor data struct
*
* Return: true if update is needed, false if not.
*/
static inline bool peci_sensor_need_update(struct peci_sensor_data *sensor)
{
return !sensor->valid ||
time_after(jiffies,
sensor->last_updated + UPDATE_INTERVAL_DEFAULT);
}
/**
* peci_sensor_need_update_with_time - check whether sensor update is needed
* or not
* @sensor: pointer to sensor data struct
* @update_interval: update interval to check
*
* Return: true if update is needed, false if not.
*/
static inline bool
peci_sensor_need_update_with_time(struct peci_sensor_data *sensor,
ulong update_interval)
{
return !sensor->valid ||
time_after(jiffies, sensor->last_updated + update_interval);
}
/**
* peci_sensor_mark_updated - mark the sensor is updated
* @sensor: pointer to sensor data struct
*/
static inline void peci_sensor_mark_updated(struct peci_sensor_data *sensor)
{
sensor->valid = 1;
sensor->last_updated = jiffies;
}
/**
* peci_sensor_mark_updated_with_time - mark the sensor is updated
* @sensor: pointer to sensor data struct
* @jif: jiffies value to update with
*/
static inline void
peci_sensor_mark_updated_with_time(struct peci_sensor_data *sensor, ulong jif)
{
sensor->valid = 1;
sensor->last_updated = jif;
}
/**
* struct peci_sensor_conf - PECI sensor information
* @attribute: Sensor attribute
* @config: Part of channel parameters brought by single sensor
* @update_interval: time in jiffies needs to elapse to read sensor again
* @read: Read callback for data attributes. Mandatory if readable
* data attributes are present.
* Parameters are:
* @module_ctx: Pointer peci module context
* @sensor_conf: Pointer to sensor configuration object
* @sensor_data: Pointer to sensor data object
* @val: Pointer to returned value
* The function returns 0 on success or a negative error number.
* @write: Write callback for data attributes. Mandatory if writeable
* data attributes are present.
* Parameters are:
* @module_ctx: Pointer peci module context
* @sensor_conf: Pointer to sensor configuration object
* @sensor_data: Pointer to sensor data object
* @val: Value to write
* The function returns 0 on success or a negative error number.
*/
struct peci_sensor_conf {
const s32 attribute;
const u32 config;
const ulong update_interval;
int (*const read)(void *priv, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data);
int (*const write)(void *priv, struct peci_sensor_conf *sensor_conf,
struct peci_sensor_data *sensor_data, s32 val);
};
/**
* peci_sensor_get_config - get peci sensor configuration for provided channel
* @sensors: Sensors list
* @sensor_count: Sensors count
*
* Return: sensor configuration
*/
static inline u32 peci_sensor_get_config(struct peci_sensor_conf sensors[],
u8 sensor_count)
{
u32 config = 0u;
int iter;
for (iter = 0; iter < sensor_count; ++iter)
config |= sensors[iter].config;
return config;
}
/**
* peci_sensor_get_ctx - get peci sensor context - both configuration and data
* @attribute: Sensor attribute
* @sensor_conf_list: Sensors configuration object list
* @sensor_conf: Sensor configuration object found
* @sensor_data_list: Sensors data object list, maybe NULL in case there is no
* need to find sensor data object
* @sensor_data: Sensor data object found, maybe NULL in case there is no need
* to find sensor data object
* @sensor_count: Sensor count
*
* Return: 0 on success or -EOPNOTSUPP in case sensor attribute not found
*/
static inline int
peci_sensor_get_ctx(s32 attribute, struct peci_sensor_conf sensor_conf_list[],
struct peci_sensor_conf **sensor_conf,
struct peci_sensor_data sensor_data_list[],
struct peci_sensor_data **sensor_data,
const u8 sensor_count)
{
int iter;
for (iter = 0; iter < sensor_count; ++iter) {
if (attribute == sensor_conf_list[iter].attribute) {
*sensor_conf = &sensor_conf_list[iter];
if (sensor_data_list && sensor_data)
*sensor_data = &sensor_data_list[iter];
return 0;
}
}
return -EOPNOTSUPP;
}
/* Value for the most common parameter used for PCS accessing */
#define PECI_PCS_PARAM_ZERO 0x0000u
#define PECI_PCS_REGISTER_SIZE 4u /* PCS register size in bytes */
/* PPL1 value to PPL2 value conversation macro */
#define PECI_PCS_PPL1_TO_PPL2(ppl1_value) ((((u32)(ppl1_value)) * 12uL) / 10uL)
#define PECI_PCS_PPL1_TIME_WINDOW 250 /* PPL1 Time Window value in ms */
#define PECI_PCS_PPL2_TIME_WINDOW 10 /* PPL2 Time Window value in ms */
/**
* union peci_pkg_power_sku_unit - PECI Package Power Unit PCS
* This register coresponds to the MSR@606h - MSR_RAPL_POWER_UNIT
* Accessing over PECI: PCS=0x1E, Parameter=0x0000
* @value: PCS register value
* @bits: PCS register bits
* @pwr_unit: Bits [3:0] - Power Unit
* @rsvd0: Bits [7:4]
* @eng_unit: Bits [12:8] - Energy Unit
* @rsvd1: Bits [15:13]
* @tim_unit: Bits [19:16] - Time Unit
* @rsvd2: Bits [31:20]
*/
union peci_pkg_power_sku_unit {
u32 value;
struct {
u32 pwr_unit : 4;
u32 rsvd0 : 4;
u32 eng_unit : 5;
u32 rsvd1 : 3;
u32 tim_unit : 4;
u32 rsvd2 : 12;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_pkg_power_sku_unit) == PECI_PCS_REGISTER_SIZE);
/**
* union peci_package_power_info_low - Platform and Package Power SKU (Low) PCS
* This PCS coresponds to the MSR@614h - PACKAGE_POWER_SKU, bits [31:0]
* Accessing over PECI: PCS=0x1C, parameter=0x00FF
* @value: PCS register value
* @bits: PCS register bits
* @pkg_tdp: Bits [14:0] - TDP Package Power
* @rsvd0: Bits [15:15]
* @pkg_min_pwr: Bits [30:16] - Minimal Package Power
* @rsvd1: Bits [31:31]
*/
union peci_package_power_info_low {
u32 value;
struct {
u32 pkg_tdp : 15;
u32 rsvd0 : 1;
u32 pkg_min_pwr : 15;
u32 rsvd1 : 1;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_package_power_info_low) ==
PECI_PCS_REGISTER_SIZE);
/**
* union peci_package_power_limit_high - Package Power Limit 2 PCS
* This PCS coresponds to the MSR@610h - PACKAGE_RAPL_LIMIT, bits [63:32]
* Accessing over PECI: PCS=0x1B, Parameter=0x0000
* @value: PCS register value
* @bits: PCS register bits
* @pwr_lim_2: Bits [14:0] - Power Limit 2
* @pwr_lim_2_en: Bits [15:15] - Power Limit 2 Enable
* @pwr_clmp_lim_2:Bits [16:16] - Package Clamping Limitation 2
* @pwr_lim_2_time:Bits [23:17] - Power Limit 2 Time Window
* @rsvd0: Bits [31:24]
*/
union peci_package_power_limit_high {
u32 value;
struct {
u32 pwr_lim_2 : 15;
u32 pwr_lim_2_en : 1;
u32 pwr_clmp_lim_2 : 1;
u32 pwr_lim_2_time : 7;
u32 rsvd0 : 8;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_package_power_limit_high) ==
PECI_PCS_REGISTER_SIZE);
/**
* union peci_package_power_limit_low - Package Power Limit 1 PCS
* This PCS coresponds to the MSR@610h - PACKAGE_RAPL_LIMIT, bits [31:0]
* Accessing over PECI: PCS=0x1A, Parameter=0x0000
* @value: PCS register value
* @bits: PCS register bits
* @pwr_lim_1: Bits [14:0] - Power Limit 1
* @pwr_lim_1_en: Bits [15:15] - Power Limit 1 Enable
* @pwr_clmp_lim_1:Bits [16:16] - Package Clamping Limitation 1
* @pwr_lim_1_time:Bits [23:17] - Power Limit 1 Time Window
* @rsvd0: Bits [31:24]
*/
union peci_package_power_limit_low {
u32 value;
struct {
u32 pwr_lim_1 : 15;
u32 pwr_lim_1_en : 1;
u32 pwr_clmp_lim_1 : 1;
u32 pwr_lim_1_time : 7;
u32 rsvd0 : 8;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_package_power_limit_low) ==
PECI_PCS_REGISTER_SIZE);
/**
* union peci_dram_power_info_low - DRAM Power Info low PCS
* This PCS coresponds to the MSR@61Ch - MSR_DRAM_POWER_INFO, bits [31:0]
* Accessing over PECI: PCS=0x24, Parameter=0x0000
* @value: PCS register value
* @bits: PCS register bits
* @tdp: Bits [14:0] - Spec DRAM Power
* @rsvd0: Bits [15:15]
* @min_pwr: Bits [30:16] - Minimal DRAM Power
* @rsvd1: Bits [31:31]
*/
union peci_dram_power_info_low {
u32 value;
struct {
u32 tdp : 15;
u32 rsvd0 : 1;
u32 min_pwr : 15;
u32 rsvd1 : 1;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_dram_power_info_low) == PECI_PCS_REGISTER_SIZE);
/**
* union peci_dram_power_limit - DRAM Power Limit PCS
* This PCS coresponds to the MSR@618h - DRAM_PLANE_POWER_LIMIT, bits [31:0]
* Accessing over PECI: PCS=0x22, Parameter=0x0000
* @value: PCS register value
* @bits: PCS register bits
* @pp_pwr_lim: Bits [14:0] - Power Limit[0] for DDR domain,
* format: U11.3
* @pwr_lim_ctrl_en:Bits [15:15] - Power Limit[0] enable bit for
* DDR domain
* @rsvd0: Bits [16:16]
* @ctrl_time_win: Bits [23:17] - Power Limit[0] time window for
* DDR domain
* @rsvd1: Bits [31:24]
*/
union peci_dram_power_limit {
u32 value;
struct {
u32 pp_pwr_lim : 15;
u32 pwr_lim_ctrl_en : 1;
u32 rsvd0 : 1;
u32 ctrl_time_win : 7;
u32 rsvd1 : 8;
} __attribute__((__packed__)) bits;
} __attribute__((__packed__));
static_assert(sizeof(union peci_dram_power_limit) == PECI_PCS_REGISTER_SIZE);
/**
* peci_pcs_xn_to_uunits - function converting value in units in x.N format to
* micro units (microjoules, microseconds, microdegrees) in regular format
* @x_n_value: Value in units in x.n format
* @n: n factor for x.n format
*
* Return: value in micro units (microjoules, microseconds, microdegrees)
* in regular format
*/
static inline u64 peci_pcs_xn_to_uunits(u32 x_n_value, u8 n)
{
u64 mx_n_value = (u64)x_n_value * 1000000uLL;
return mx_n_value >> n;
}
/**
* peci_pcs_xn_to_munits - function converting value in units in x.N format to
* milli units (millijoules, milliseconds, millidegrees) in regular format
* @x_n_value: Value in units in x.n format
* @n: n factor for x.n format
*
* Return: value in milli units (millijoules, milliseconds, millidegrees)
* in regular format
*/
static inline u64 peci_pcs_xn_to_munits(u32 x_n_value, u8 n)
{
u64 mx_n_value = (u64)x_n_value * 1000uLL;
return mx_n_value >> n;
}
/**
* peci_pcs_munits_to_xn - function converting value in milli units
* (millijoules,milliseconds, millidegrees) in regular format to value in units
* in x.n format
* @mu_value: Value in milli units (millijoules, milliseconds, millidegrees)
* @n: n factor for x.n format, assumed here maximal value for n is 32
*
* Return: value in units in x.n format
*/
static inline u32 peci_pcs_munits_to_xn(u32 mu_value, u8 n)
{
/* Convert value in milli units (regular format) to the x.n format */
u64 mx_n_value = (u64)mu_value << n;
/* Convert milli units (x.n format) to units (x.n format) */
if (mx_n_value > (u64)U32_MAX) {
do_div(mx_n_value, 1000uL);
return (u32)mx_n_value;
} else {
return (u32)mx_n_value / 1000uL;
}
}
/**
* peci_pcs_read - read PCS register
* @peci_mgr: PECI client manager handle
* @index: PCS index
* @parameter: PCS parameter
* @reg: Pointer to the variable read value is going to be put
*
* Return: 0 if succeeded,
* -EINVAL if there are null pointers among arguments,
* other values in case other errors.
*/
static inline int peci_pcs_read(struct peci_client_manager *peci_mgr, u8 index,
u16 parameter, u32 *reg)
{
u32 pcs_reg;
int ret;
if (!reg)
return -EINVAL;
ret = peci_client_read_package_config(peci_mgr, index, parameter,
(u8 *)&pcs_reg);
if (!ret)
*reg = le32_to_cpup((__le32 *)&pcs_reg);
return ret;
}
/**
* peci_pcs_write - write PCS register
* @peci_mgr: PECI client manager handle
* @index: PCS index
* @parameter: PCS parameter
* @reg: Variable which value is going to be written to the PCS
*
* Return: 0 if succeeded, other values in case an error.
*/
static inline int peci_pcs_write(struct peci_client_manager *peci_mgr, u8 index,
u16 parameter, u32 reg)
{
int ret;
ret = peci_client_write_package_config(peci_mgr, index, parameter, reg);
return ret;
}
/**
* peci_pcs_calc_pwr_from_eng - calculate power (in milliwatts) based on
* two energy readings
* @dev: Device handle
* @prev_energy: Previous energy reading context with raw energy counter value
* @energy: Current energy reading context with raw energy counter value
* @unit: Calculation factor
* @power_val_in_mW: Pointer to the variable calculation result is going to
* be put
*
* Return: 0 if succeeded,
* -EINVAL if there are null pointers among arguments,
* -EAGAIN if calculation is skipped.
*/
static inline int peci_pcs_calc_pwr_from_eng(struct device *dev,
struct peci_sensor_data *prev_energy,
struct peci_sensor_data *energy,
u32 unit, s32 *power_in_mW)
{
ulong elapsed;
int ret;
elapsed = energy->last_updated - prev_energy->last_updated;
dev_dbg(dev, "raw energy before %u, raw energy now %u, unit %u, jiffies elapsed %lu\n",
prev_energy->uvalue, energy->uvalue, unit, elapsed);
/*
* TODO: Remove checking current energy value against 0.
* During host reset CPU resets its energy counters, hwmon treats such case
* as proper energy read (counter overflow) and calculates invalid
* power consumption. Currently hwmon is unable to determine if CPU was
* reset, stop treating 0 as invalid value when proper mechanism
* is implemented.
*
* Don't calculate average power for first counter read last counter
* read was more than 60 minutes ago (jiffies did not wrap and power
* calculation does not overflow or underflow) or energy read time
* did not change.
*/
if (energy->uvalue > 0 && prev_energy->last_updated > 0 &&
elapsed < (HZ * 3600) && elapsed) {
u32 energy_consumed;
u64 energy_consumed_in_mJ;
u64 energy_by_jiffies;
if (energy->uvalue >= prev_energy->uvalue)
energy_consumed = energy->uvalue - prev_energy->uvalue;
else
energy_consumed = (U32_MAX - prev_energy->uvalue) +
energy->uvalue + 1u;
energy_consumed_in_mJ =
peci_pcs_xn_to_munits(energy_consumed, unit);
energy_by_jiffies = energy_consumed_in_mJ * HZ;
if (energy_by_jiffies > (u64)U32_MAX) {
do_div(energy_by_jiffies, elapsed);
*power_in_mW = (long)energy_by_jiffies;
} else {
*power_in_mW = (u32)energy_by_jiffies / elapsed;
}
dev_dbg(dev, "raw energy consumed %u, scaled energy consumed %llumJ, scaled power %dmW\n",
energy_consumed, energy_consumed_in_mJ, *power_in_mW);
ret = 0;
} else {
dev_dbg(dev, "skipping calculate power, try again\n");
*power_in_mW = 0;
ret = -EAGAIN;
}
prev_energy->uvalue = energy->uvalue;
peci_sensor_mark_updated_with_time(prev_energy, energy->last_updated);
return ret;
}
/**
* peci_pcs_calc_acc_eng - calculate accumulated energy (in microjoules) based
* on two energy readings
* @dev: Device handle
* @prev_energy: Previous energy reading context with raw energy counter value
* @energy: Current energy reading context with raw energy counter value
* @unit: Calculation factor
* @acc_energy_in_uJ: Pointer to the variable with cumulative energy counter
*
* Return: 0 if succeeded,
* -EINVAL if there are null pointers among arguments,
* -EAGAIN if calculation is skipped.
*/
static inline int peci_pcs_calc_acc_eng(struct device *dev,
struct peci_sensor_data *prev_energy,
struct peci_sensor_data *curr_energy,
u32 unit, u32 *acc_energy_in_uJ)
{
ulong elapsed;
int ret;
elapsed = curr_energy->last_updated - prev_energy->last_updated;
dev_dbg(dev, "raw energy before %u, raw energy now %u, unit %u, jiffies elapsed %lu\n",
prev_energy->uvalue, curr_energy->uvalue, unit, elapsed);
/*
* TODO: Remove checking current energy value against 0.
* During host reset CPU resets its energy counters, hwmon treats such case
* as proper energy read (counter overflow) and calculates invalid
* energy increase. Currently hwmon is unable to determine if CPU was
* reset, stop treating 0 as invalid value when proper mechanism
* is implemented.
*
* Don't calculate cumulative energy for first counter read - last counter
* read was more than 17 minutes ago (jiffies and energy raw counter did not wrap
* and power calculation does not overflow or underflow).
*/
if (curr_energy->uvalue > 0 && prev_energy->last_updated > 0 &&
elapsed < (HZ * 17 * 60)) {
u32 energy_consumed;
u64 energy_consumed_in_uJ;
if (curr_energy->uvalue >= prev_energy->uvalue)
energy_consumed = curr_energy->uvalue -
prev_energy->uvalue;
else
energy_consumed = (U32_MAX - prev_energy->uvalue) +
curr_energy->uvalue + 1u;
energy_consumed_in_uJ =
peci_pcs_xn_to_uunits(energy_consumed, unit);
*acc_energy_in_uJ = S32_MAX &
(*acc_energy_in_uJ + (u32)energy_consumed_in_uJ);
dev_dbg(dev, "raw energy %u, scaled energy %llumJ, cumulative energy %dmJ\n",
energy_consumed, energy_consumed_in_uJ,
*acc_energy_in_uJ);
ret = 0;
} else {
dev_dbg(dev, "skipping calculate cumulative energy, try again\n");
*acc_energy_in_uJ = 0;
ret = -EAGAIN;
}
prev_energy->uvalue = curr_energy->uvalue;
peci_sensor_mark_updated_with_time(prev_energy,
curr_energy->last_updated);
return ret;
}
/**
* peci_pcs_get_units - read units (power, energy, time) from HW or cache
* @peci_mgr: PECI client manager handle
* @units: Pointer to the variable read value is going to be put in case reading
* from HW
* @valid: Flag telling cache is valid
*
* Return: 0 if succeeded
* -EINVAL if there are null pointers among arguments,
* other values in case other errors.
*/
static inline int peci_pcs_get_units(struct peci_client_manager *peci_mgr,
union peci_pkg_power_sku_unit *units,
bool *valid)
{
int ret = 0;
if (!valid)
return -EINVAL;
if (!(*valid)) {
ret = peci_pcs_read(peci_mgr, PECI_MBX_INDEX_TDP_UNITS,
PECI_PCS_PARAM_ZERO, &units->value);
if (!ret)
*valid = true;
}
return ret;
}
/**
* peci_pcs_calc_plxy_time_window - calculate power limit time window in
* PCS format. To figure that value out needs to solve the following equation:
* time_window = (1+(x/4)) * (2 ^ y), where time_window is known value and
* x and y values are variables to find.
* Return value is about X & Y compostion according to the following:
* x = ret[6:5], y = ret[4:0].
* @pl_tim_wnd_in_xn: PPL time window in X-n format
*
* Return: Power limit time window value
*/
static inline u32 peci_pcs_calc_plxy_time_window(u32 pl_tim_wnd_in_xn)
{
u32 x = 0u;
u32 y = 0u;
/* Calculate y first */
while (pl_tim_wnd_in_xn > 7u) {
pl_tim_wnd_in_xn >>= 1;
y++;
}
/* Correct y value */
if (pl_tim_wnd_in_xn >= 4u)
y += 2u;
else if (pl_tim_wnd_in_xn >= 2u)
y += 1u;
/* Calculate x then */
if (pl_tim_wnd_in_xn >= 4u)
x = pl_tim_wnd_in_xn % 4;
else
x = 0u;
return ((x & 0x3) << 5) | (y & 0x1F);
}
#endif /* __PECI_HWMON_H */