/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (c) 2018-2020 Intel Corporation */ #ifndef __PECI_HWMON_H #define __PECI_HWMON_H #include #include #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 */