// SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2018-2020 Intel Corporation #include #include #include #include #include #include #include "peci-hwmon.h" enum PECI_CPUPOWER_POWER_SENSOR_TYPES { PECI_CPUPOWER_SENSOR_TYPE_POWER = 0, PECI_CPUPOWER_SENSOR_TYPE_ENERGY, PECI_CPUPOWER_SENSOR_TYPES_COUNT, }; #define PECI_CPUPOWER_POWER_CHANNEL_COUNT 1 /* Supported channels number */ #define PECI_CPUPOWER_ENERGY_CHANNEL_COUNT 1 /* Supported channels number */ #define PECI_CPUPOWER_POWER_SENSOR_COUNT 4 /* Supported sensors number */ #define PECI_CPUPOWER_ENERGY_SENSOR_COUNT 1 /* Supported sensors number */ struct peci_cpupower { struct device *dev; struct peci_client_manager *mgr; char name[PECI_NAME_SIZE]; u32 power_config[PECI_CPUPOWER_POWER_CHANNEL_COUNT + 1]; u32 energy_config[PECI_CPUPOWER_ENERGY_CHANNEL_COUNT + 1]; struct hwmon_channel_info power_info; struct hwmon_channel_info energy_info; const struct hwmon_channel_info *info[PECI_CPUPOWER_SENSOR_TYPES_COUNT + 1]; struct hwmon_chip_info chip; struct peci_sensor_data power_sensor_data_list[PECI_CPUPOWER_POWER_CHANNEL_COUNT] [PECI_CPUPOWER_POWER_SENSOR_COUNT]; struct peci_sensor_data energy_sensor_data_list[PECI_CPUPOWER_ENERGY_CHANNEL_COUNT] [PECI_CPUPOWER_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 ppl1_time_window; u32 ppl2_time_window; bool ppl_time_windows_valid; }; static const char *peci_cpupower_labels[PECI_CPUPOWER_SENSOR_TYPES_COUNT] = { "cpu power", "cpu energy", }; /** * peci_cpupower_read_cpu_pkg_pwr_info_low - read PCS Platform Power SKU low. * @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_cpupower_read_cpu_pkg_pwr_info_low(struct peci_client_manager *peci_mgr, union peci_package_power_info_low *reg) { return peci_pcs_read(peci_mgr, PECI_MBX_INDEX_TDP, PECI_PKG_ID_CPU_PACKAGE, ®->value); } /** * peci_cpupower_read_cpu_pkg_pwr_lim_low - read PCS Package Power Limit Low * @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_cpupower_read_cpu_pkg_pwr_lim_low(struct peci_client_manager *peci_mgr, union peci_package_power_limit_low *reg) { return peci_pcs_read(peci_mgr, PECI_MBX_INDEX_PKG_POWER_LIMIT1, PECI_PCS_PARAM_ZERO, ®->value); } static int peci_cpupower_get_energy_counter(struct peci_cpupower *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 package energy over peci\n"); goto unlock; } ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_ENERGY_COUNTER, PECI_PKG_ID_CPU_PACKAGE, &sensor_data->uvalue); if (ret) { dev_dbg(priv->dev, "not able to read package 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_cpupower_get_average_power(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data) { struct peci_cpupower *priv = (struct peci_cpupower *)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 power value %dmW jif %lu\n", sensor_data->value, jiffies); goto unlock; } ret = peci_cpupower_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_cpupower_get_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data) { struct peci_cpupower *priv = (struct peci_cpupower *)ctx; union peci_package_power_limit_low 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_cpupower_read_cpu_pkg_pwr_lim_low(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.pwr_lim_1, priv->units.bits.pwr_unit); dev_dbg(priv->dev, "raw power limit %u, unit %u, power limit %d\n", power_limit.bits.pwr_lim_1, priv->units.bits.pwr_unit, sensor_data->value); unlock: mutex_unlock(&sensor_data->lock); return ret; } static int peci_cpupower_set_power_limit(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data, s32 val) { struct peci_cpupower *priv = (struct peci_cpupower *)ctx; union peci_package_power_limit_high power_limit_high; union peci_package_power_limit_low power_limit_low; 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_cpupower_read_cpu_pkg_pwr_lim_low(priv->mgr, &power_limit_low); if (ret) { dev_dbg(priv->dev, "not able to read package power limit 1\n"); return ret; } ret = peci_pcs_read(priv->mgr, PECI_MBX_INDEX_PKG_POWER_LIMIT2, PECI_PCS_PARAM_ZERO, &power_limit_high.value); if (ret) { dev_dbg(priv->dev, "not able to read package power limit 2\n"); return ret; } /* Calculate PPL time windows if needed */ if (!priv->ppl_time_windows_valid) { priv->ppl1_time_window = peci_pcs_calc_plxy_time_window(peci_pcs_munits_to_xn( PECI_PCS_PPL1_TIME_WINDOW, priv->units.bits.tim_unit)); priv->ppl2_time_window = peci_pcs_calc_plxy_time_window(peci_pcs_munits_to_xn( PECI_PCS_PPL2_TIME_WINDOW, priv->units.bits.tim_unit)); priv->ppl_time_windows_valid = true; } /* Enable or disable power limitation */ if (val > 0) { /* Set PPL1 */ power_limit_low.bits.pwr_lim_1 = peci_pcs_munits_to_xn(val, priv->units.bits.pwr_unit); power_limit_low.bits.pwr_lim_1_en = 1u; power_limit_low.bits.pwr_clmp_lim_1 = 1u; power_limit_low.bits.pwr_lim_1_time = priv->ppl1_time_window; /* Set PPL2 */ power_limit_high.bits.pwr_lim_2 = peci_pcs_munits_to_xn(PECI_PCS_PPL1_TO_PPL2(val), priv->units.bits.pwr_unit); power_limit_high.bits.pwr_lim_2_en = 1u; power_limit_high.bits.pwr_clmp_lim_2 = 1u; power_limit_high.bits.pwr_lim_2_time = priv->ppl2_time_window; } else { power_limit_low.bits.pwr_lim_1 = 0u; power_limit_low.bits.pwr_lim_1_en = 0u; power_limit_low.bits.pwr_clmp_lim_1 = 0u; power_limit_low.bits.pwr_lim_1_time = 0u; power_limit_high.bits.pwr_lim_2 = 0u; power_limit_high.bits.pwr_lim_2_en = 0u; power_limit_high.bits.pwr_clmp_lim_2 = 0u; power_limit_high.bits.pwr_lim_2_time = 0u; } ret = peci_pcs_write(priv->mgr, PECI_MBX_INDEX_PKG_POWER_LIMIT1, PECI_PCS_PARAM_ZERO, power_limit_low.value); if (ret) { dev_dbg(priv->dev, "not able to write package power limit 1\n"); return ret; } ret = peci_pcs_write(priv->mgr, PECI_MBX_INDEX_PKG_POWER_LIMIT2, PECI_PCS_PARAM_ZERO, power_limit_high.value); if (ret) { dev_dbg(priv->dev, "not able to write package power limit 2\n"); return ret; } dev_dbg(priv->dev, "power limit %d, unit %u, raw package power limit 1 %u,\n", val, priv->units.bits.pwr_unit, power_limit_low.bits.pwr_lim_1); return ret; } static int peci_cpupower_read_max_power(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data) { struct peci_cpupower *priv = (struct peci_cpupower *)ctx; union peci_package_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_cpupower_read_cpu_pkg_pwr_info_low(priv->mgr, &power_info); if (ret) { dev_dbg(priv->dev, "not able to read package power info\n"); goto unlock; } peci_sensor_mark_updated(sensor_data); sensor_data->value = peci_pcs_xn_to_munits(power_info.bits.pkg_tdp, priv->units.bits.pwr_unit); dev_dbg(priv->dev, "raw max power %u, unit %u, max power %dmW\n", power_info.bits.pkg_tdp, priv->units.bits.pwr_unit, sensor_data->value); unlock: mutex_unlock(&sensor_data->lock); return ret; } static int peci_cpupower_read_min_power(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data) { struct peci_cpupower *priv = (struct peci_cpupower *)ctx; union peci_package_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, min 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_cpupower_read_cpu_pkg_pwr_info_low(priv->mgr, &power_info); if (ret) { dev_dbg(priv->dev, "not able to read package power info\n"); goto unlock; } peci_sensor_mark_updated(sensor_data); sensor_data->value = peci_pcs_xn_to_munits(power_info.bits.pkg_min_pwr, priv->units.bits.pwr_unit); dev_dbg(priv->dev, "raw min power %u, unit %u, min power %dmW\n", power_info.bits.pkg_min_pwr, priv->units.bits.pwr_unit, sensor_data->value); unlock: mutex_unlock(&sensor_data->lock); return ret; } static int peci_cpupower_read_energy(void *ctx, struct peci_sensor_conf *sensor_conf, struct peci_sensor_data *sensor_data) { struct peci_cpupower *priv = (struct peci_cpupower *)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 %uuJ jif %lu\n", sensor_data->uvalue, jiffies); goto unlock; } ret = peci_cpupower_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 0; } static struct peci_sensor_conf peci_cpupower_power_cfg[PECI_CPUPOWER_POWER_CHANNEL_COUNT] [PECI_CPUPOWER_POWER_SENSOR_COUNT] = { /* Channel 0 - Power */ { { .attribute = hwmon_power_average, .config = HWMON_P_AVERAGE, .update_interval = UPDATE_INTERVAL_100MS, .read = peci_cpupower_get_average_power, .write = NULL, }, { .attribute = hwmon_power_cap, .config = HWMON_P_CAP, .update_interval = UPDATE_INTERVAL_100MS, .read = peci_cpupower_get_power_limit, .write = peci_cpupower_set_power_limit, }, { .attribute = hwmon_power_cap_max, .config = HWMON_P_CAP_MAX, .update_interval = UPDATE_INTERVAL_10S, .read = peci_cpupower_read_max_power, .write = NULL, }, { .attribute = hwmon_power_cap_min, .config = HWMON_P_CAP_MIN, .update_interval = UPDATE_INTERVAL_10S, .read = peci_cpupower_read_min_power, .write = NULL, }, }, }; static struct peci_sensor_conf peci_cpupower_energy_cfg[PECI_CPUPOWER_ENERGY_CHANNEL_COUNT] [PECI_CPUPOWER_ENERGY_SENSOR_COUNT] = { /* Channel 0 - Energy */ { { .attribute = hwmon_energy_input, .config = HWMON_E_INPUT, .update_interval = UPDATE_INTERVAL_100MS, .read = peci_cpupower_read_energy, .write = NULL, }, } }; static bool peci_cpupower_is_channel_valid(enum hwmon_sensor_types type, int channel) { if ((type == hwmon_power && channel < PECI_CPUPOWER_POWER_CHANNEL_COUNT) || (type == hwmon_energy && channel < PECI_CPUPOWER_ENERGY_CHANNEL_COUNT)) return true; return false; } static int peci_cpupower_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { if (!peci_cpupower_is_channel_valid(type, channel)) return -EOPNOTSUPP; switch (attr) { case hwmon_power_label: *str = peci_cpupower_labels[PECI_CPUPOWER_SENSOR_TYPE_POWER]; break; case hwmon_energy_label: *str = peci_cpupower_labels[PECI_CPUPOWER_SENSOR_TYPE_ENERGY]; break; default: return -EOPNOTSUPP; } return 0; } static int peci_cpupower_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct peci_cpupower *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_cpupower_is_channel_valid(type, channel)) return -EOPNOTSUPP; switch (type) { case hwmon_power: ret = peci_sensor_get_ctx(attr, peci_cpupower_power_cfg[channel], &sensor_conf, priv->power_sensor_data_list[channel], &sensor_data, ARRAY_SIZE(peci_cpupower_power_cfg[channel])); break; case hwmon_energy: ret = peci_sensor_get_ctx(attr, peci_cpupower_energy_cfg[channel], &sensor_conf, priv->energy_sensor_data_list[channel], &sensor_data, ARRAY_SIZE(peci_cpupower_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_cpupower_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct peci_cpupower *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_cpupower_is_channel_valid(type, channel)) return -EOPNOTSUPP; switch (type) { case hwmon_power: ret = peci_sensor_get_ctx(attr, peci_cpupower_power_cfg[channel], &sensor_conf, priv->power_sensor_data_list[channel], &sensor_data, ARRAY_SIZE(peci_cpupower_power_cfg[channel])); break; case hwmon_energy: ret = peci_sensor_get_ctx(attr, peci_cpupower_energy_cfg[channel], &sensor_conf, priv->energy_sensor_data_list[channel], &sensor_data, ARRAY_SIZE(peci_cpupower_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_cpupower_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_cpupower_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_cpupower_power_cfg[channel], &sensor_conf, NULL, NULL, ARRAY_SIZE(peci_cpupower_power_cfg[channel])); break; case hwmon_energy: ret = peci_sensor_get_ctx(attr, peci_cpupower_energy_cfg[channel], &sensor_conf, NULL, NULL, ARRAY_SIZE(peci_cpupower_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_cpupower_ops = { .is_visible = peci_cpupower_is_visible, .read_string = peci_cpupower_read_string, .read = peci_cpupower_read, .write = peci_cpupower_write, }; static void peci_cpupower_sensor_init(struct peci_cpupower *priv) { int i, j; mutex_init(&priv->energy_cache.lock); for (i = 0; i < PECI_CPUPOWER_POWER_CHANNEL_COUNT; i++) { for (j = 0; j < PECI_CPUPOWER_POWER_SENSOR_COUNT; j++) mutex_init(&priv->power_sensor_data_list[i][j].lock); } for (i = 0; i < PECI_CPUPOWER_ENERGY_CHANNEL_COUNT; i++) { for (j = 0; j < PECI_CPUPOWER_ENERGY_SENSOR_COUNT; j++) mutex_init(&priv->energy_sensor_data_list[i][j].lock); } } static int peci_cpupower_probe(struct platform_device *pdev) { struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent); struct device *dev = &pdev->dev; struct peci_cpupower *priv; struct device *hwmon_dev; u32 power_cfg_idx = 0; u32 energy_cfg_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_cpupower.cpu%d.%d", mgr->client->addr - PECI_BASE_ADDR, mgr->client->domain_id); priv->power_config[power_cfg_idx] = HWMON_P_LABEL | peci_sensor_get_config(peci_cpupower_power_cfg[power_cfg_idx], ARRAY_SIZE(peci_cpupower_power_cfg[power_cfg_idx])); priv->energy_config[energy_cfg_idx] = HWMON_E_LABEL | peci_sensor_get_config(peci_cpupower_energy_cfg[energy_cfg_idx], ARRAY_SIZE(peci_cpupower_energy_cfg[energy_cfg_idx])); priv->info[PECI_CPUPOWER_SENSOR_TYPE_POWER] = &priv->power_info; priv->power_info.type = hwmon_power; priv->power_info.config = priv->power_config; priv->info[PECI_CPUPOWER_SENSOR_TYPE_ENERGY] = &priv->energy_info; priv->energy_info.type = hwmon_energy; priv->energy_info.config = priv->energy_config; priv->chip.ops = &peci_cpupower_ops; priv->chip.info = priv->info; peci_cpupower_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_cpupower_ids[] = { { .name = "peci-cpupower", .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(platform, peci_cpupower_ids); static struct platform_driver peci_cpupower_driver = { .probe = peci_cpupower_probe, .id_table = peci_cpupower_ids, .driver = { .name = KBUILD_MODNAME, }, }; module_platform_driver(peci_cpupower_driver); MODULE_AUTHOR("Zhikui Ren "); MODULE_DESCRIPTION("PECI cpupower driver"); MODULE_LICENSE("GPL v2");