| // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
| // Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved |
| |
| #include <linux/hwmon.h> |
| #include <linux/bitmap.h> |
| #include <linux/mlx5/device.h> |
| #include <linux/mlx5/mlx5_ifc.h> |
| #include <linux/mlx5/port.h> |
| #include "mlx5_core.h" |
| #include "hwmon.h" |
| |
| #define CHANNELS_TYPE_NUM 2 /* chip channel and temp channel */ |
| #define CHIP_CONFIG_NUM 1 |
| |
| /* module 0 is mapped to sensor_index 64 in MTMP register */ |
| #define to_mtmp_module_sensor_idx(idx) (64 + (idx)) |
| |
| /* All temperatures retrieved in units of 0.125C. hwmon framework expect |
| * it in units of millidegrees C. Hence multiply values by 125. |
| */ |
| #define mtmp_temp_to_mdeg(temp) ((temp) * 125) |
| |
| struct temp_channel_desc { |
| u32 sensor_index; |
| char sensor_name[32]; |
| }; |
| |
| /* chip_channel_config and channel_info arrays must be 0-terminated, hence + 1 */ |
| struct mlx5_hwmon { |
| struct mlx5_core_dev *mdev; |
| struct device *hwmon_dev; |
| struct hwmon_channel_info chip_info; |
| u32 chip_channel_config[CHIP_CONFIG_NUM + 1]; |
| struct hwmon_channel_info temp_info; |
| u32 *temp_channel_config; |
| const struct hwmon_channel_info *channel_info[CHANNELS_TYPE_NUM + 1]; |
| struct hwmon_chip_info chip; |
| struct temp_channel_desc *temp_channel_desc; |
| u32 asic_platform_scount; |
| u32 module_scount; |
| }; |
| |
| static int mlx5_hwmon_query_mtmp(struct mlx5_core_dev *mdev, u32 sensor_index, u32 *mtmp_out) |
| { |
| u32 mtmp_in[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| |
| MLX5_SET(mtmp_reg, mtmp_in, sensor_index, sensor_index); |
| |
| return mlx5_core_access_reg(mdev, mtmp_in, sizeof(mtmp_in), |
| mtmp_out, MLX5_ST_SZ_BYTES(mtmp_reg), |
| MLX5_REG_MTMP, 0, 0); |
| } |
| |
| static int mlx5_hwmon_reset_max_temp(struct mlx5_core_dev *mdev, int sensor_index) |
| { |
| u32 mtmp_out[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| u32 mtmp_in[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| |
| MLX5_SET(mtmp_reg, mtmp_in, sensor_index, sensor_index); |
| MLX5_SET(mtmp_reg, mtmp_in, mtr, 1); |
| |
| return mlx5_core_access_reg(mdev, mtmp_in, sizeof(mtmp_in), |
| mtmp_out, sizeof(mtmp_out), |
| MLX5_REG_MTMP, 0, 0); |
| } |
| |
| static int mlx5_hwmon_enable_max_temp(struct mlx5_core_dev *mdev, int sensor_index) |
| { |
| u32 mtmp_out[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| u32 mtmp_in[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| int err; |
| |
| err = mlx5_hwmon_query_mtmp(mdev, sensor_index, mtmp_in); |
| if (err) |
| return err; |
| |
| MLX5_SET(mtmp_reg, mtmp_in, mte, 1); |
| return mlx5_core_access_reg(mdev, mtmp_in, sizeof(mtmp_in), |
| mtmp_out, sizeof(mtmp_out), |
| MLX5_REG_MTMP, 0, 1); |
| } |
| |
| static int mlx5_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
| int channel, long *val) |
| { |
| struct mlx5_hwmon *hwmon = dev_get_drvdata(dev); |
| u32 mtmp_out[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| int err; |
| |
| if (type != hwmon_temp) |
| return -EOPNOTSUPP; |
| |
| err = mlx5_hwmon_query_mtmp(hwmon->mdev, hwmon->temp_channel_desc[channel].sensor_index, |
| mtmp_out); |
| if (err) |
| return err; |
| |
| switch (attr) { |
| case hwmon_temp_input: |
| *val = mtmp_temp_to_mdeg(MLX5_GET(mtmp_reg, mtmp_out, temperature)); |
| return 0; |
| case hwmon_temp_highest: |
| *val = mtmp_temp_to_mdeg(MLX5_GET(mtmp_reg, mtmp_out, max_temperature)); |
| return 0; |
| case hwmon_temp_crit: |
| *val = mtmp_temp_to_mdeg(MLX5_GET(mtmp_reg, mtmp_out, temp_threshold_hi)); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int mlx5_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
| int channel, long val) |
| { |
| struct mlx5_hwmon *hwmon = dev_get_drvdata(dev); |
| |
| if (type != hwmon_temp || attr != hwmon_temp_reset_history) |
| return -EOPNOTSUPP; |
| |
| return mlx5_hwmon_reset_max_temp(hwmon->mdev, |
| hwmon->temp_channel_desc[channel].sensor_index); |
| } |
| |
| static umode_t mlx5_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, |
| int channel) |
| { |
| if (type != hwmon_temp) |
| return 0; |
| |
| switch (attr) { |
| case hwmon_temp_input: |
| case hwmon_temp_highest: |
| case hwmon_temp_crit: |
| case hwmon_temp_label: |
| return 0444; |
| case hwmon_temp_reset_history: |
| return 0200; |
| default: |
| return 0; |
| } |
| } |
| |
| static int mlx5_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
| int channel, const char **str) |
| { |
| struct mlx5_hwmon *hwmon = dev_get_drvdata(dev); |
| |
| if (type != hwmon_temp || attr != hwmon_temp_label) |
| return -EOPNOTSUPP; |
| |
| *str = (const char *)hwmon->temp_channel_desc[channel].sensor_name; |
| return 0; |
| } |
| |
| static const struct hwmon_ops mlx5_hwmon_ops = { |
| .read = mlx5_hwmon_read, |
| .read_string = mlx5_hwmon_read_string, |
| .is_visible = mlx5_hwmon_is_visible, |
| .write = mlx5_hwmon_write, |
| }; |
| |
| static int mlx5_hwmon_init_channels_names(struct mlx5_hwmon *hwmon) |
| { |
| u32 i; |
| |
| for (i = 0; i < hwmon->asic_platform_scount + hwmon->module_scount; i++) { |
| u32 mtmp_out[MLX5_ST_SZ_DW(mtmp_reg)] = {}; |
| char *sensor_name; |
| int err; |
| |
| err = mlx5_hwmon_query_mtmp(hwmon->mdev, hwmon->temp_channel_desc[i].sensor_index, |
| mtmp_out); |
| if (err) |
| return err; |
| |
| sensor_name = MLX5_ADDR_OF(mtmp_reg, mtmp_out, sensor_name_hi); |
| if (!*sensor_name) { |
| snprintf(hwmon->temp_channel_desc[i].sensor_name, |
| sizeof(hwmon->temp_channel_desc[i].sensor_name), "sensor%u", |
| hwmon->temp_channel_desc[i].sensor_index); |
| continue; |
| } |
| |
| memcpy(&hwmon->temp_channel_desc[i].sensor_name, sensor_name, |
| MLX5_FLD_SZ_BYTES(mtmp_reg, sensor_name_hi) + |
| MLX5_FLD_SZ_BYTES(mtmp_reg, sensor_name_lo)); |
| } |
| |
| return 0; |
| } |
| |
| static int mlx5_hwmon_get_module_sensor_index(struct mlx5_core_dev *mdev, u32 *module_index) |
| { |
| int module_num; |
| int err; |
| |
| err = mlx5_query_module_num(mdev, &module_num); |
| if (err) |
| return err; |
| |
| *module_index = to_mtmp_module_sensor_idx(module_num); |
| |
| return 0; |
| } |
| |
| static int mlx5_hwmon_init_sensors_indexes(struct mlx5_hwmon *hwmon, u64 sensor_map) |
| { |
| DECLARE_BITMAP(smap, BITS_PER_TYPE(sensor_map)); |
| unsigned long bit_pos; |
| int err = 0; |
| int i = 0; |
| |
| bitmap_from_u64(smap, sensor_map); |
| |
| for_each_set_bit(bit_pos, smap, BITS_PER_TYPE(sensor_map)) { |
| hwmon->temp_channel_desc[i].sensor_index = bit_pos; |
| i++; |
| } |
| |
| if (hwmon->module_scount) |
| err = mlx5_hwmon_get_module_sensor_index(hwmon->mdev, |
| &hwmon->temp_channel_desc[i].sensor_index); |
| |
| return err; |
| } |
| |
| static void mlx5_hwmon_channel_info_init(struct mlx5_hwmon *hwmon) |
| { |
| int i; |
| |
| hwmon->channel_info[0] = &hwmon->chip_info; |
| hwmon->channel_info[1] = &hwmon->temp_info; |
| |
| hwmon->chip_channel_config[0] = HWMON_C_REGISTER_TZ; |
| hwmon->chip_info.config = (const u32 *)hwmon->chip_channel_config; |
| hwmon->chip_info.type = hwmon_chip; |
| |
| for (i = 0; i < hwmon->asic_platform_scount + hwmon->module_scount; i++) |
| hwmon->temp_channel_config[i] = HWMON_T_INPUT | HWMON_T_HIGHEST | HWMON_T_CRIT | |
| HWMON_T_RESET_HISTORY | HWMON_T_LABEL; |
| |
| hwmon->temp_info.config = (const u32 *)hwmon->temp_channel_config; |
| hwmon->temp_info.type = hwmon_temp; |
| } |
| |
| static int mlx5_hwmon_is_module_mon_cap(struct mlx5_core_dev *mdev, bool *mon_cap) |
| { |
| u32 mtmp_out[MLX5_ST_SZ_DW(mtmp_reg)]; |
| u32 module_index; |
| int err; |
| |
| err = mlx5_hwmon_get_module_sensor_index(mdev, &module_index); |
| if (err) |
| return err; |
| |
| err = mlx5_hwmon_query_mtmp(mdev, module_index, mtmp_out); |
| if (err) |
| return err; |
| |
| if (MLX5_GET(mtmp_reg, mtmp_out, temperature)) |
| *mon_cap = true; |
| |
| return 0; |
| } |
| |
| static int mlx5_hwmon_get_sensors_count(struct mlx5_core_dev *mdev, u32 *asic_platform_scount) |
| { |
| u32 mtcap_out[MLX5_ST_SZ_DW(mtcap_reg)] = {}; |
| u32 mtcap_in[MLX5_ST_SZ_DW(mtcap_reg)] = {}; |
| int err; |
| |
| err = mlx5_core_access_reg(mdev, mtcap_in, sizeof(mtcap_in), |
| mtcap_out, sizeof(mtcap_out), |
| MLX5_REG_MTCAP, 0, 0); |
| if (err) |
| return err; |
| |
| *asic_platform_scount = MLX5_GET(mtcap_reg, mtcap_out, sensor_count); |
| |
| return 0; |
| } |
| |
| static void mlx5_hwmon_free(struct mlx5_hwmon *hwmon) |
| { |
| if (!hwmon) |
| return; |
| |
| kfree(hwmon->temp_channel_config); |
| kfree(hwmon->temp_channel_desc); |
| kfree(hwmon); |
| } |
| |
| static struct mlx5_hwmon *mlx5_hwmon_alloc(struct mlx5_core_dev *mdev) |
| { |
| struct mlx5_hwmon *hwmon; |
| bool mon_cap = false; |
| u32 sensors_count; |
| int err; |
| |
| hwmon = kzalloc(sizeof(*mdev->hwmon), GFP_KERNEL); |
| if (!hwmon) |
| return ERR_PTR(-ENOMEM); |
| |
| err = mlx5_hwmon_get_sensors_count(mdev, &hwmon->asic_platform_scount); |
| if (err) |
| goto err_free_hwmon; |
| |
| /* check if module sensor has thermal mon cap. if yes, allocate channel desc for it */ |
| err = mlx5_hwmon_is_module_mon_cap(mdev, &mon_cap); |
| if (err) |
| goto err_free_hwmon; |
| |
| hwmon->module_scount = mon_cap ? 1 : 0; |
| sensors_count = hwmon->asic_platform_scount + hwmon->module_scount; |
| hwmon->temp_channel_desc = kcalloc(sensors_count, sizeof(*hwmon->temp_channel_desc), |
| GFP_KERNEL); |
| if (!hwmon->temp_channel_desc) { |
| err = -ENOMEM; |
| goto err_free_hwmon; |
| } |
| |
| /* sensors configuration values array, must be 0-terminated hence, + 1 */ |
| hwmon->temp_channel_config = kcalloc(sensors_count + 1, sizeof(*hwmon->temp_channel_config), |
| GFP_KERNEL); |
| if (!hwmon->temp_channel_config) { |
| err = -ENOMEM; |
| goto err_free_temp_channel_desc; |
| } |
| |
| hwmon->mdev = mdev; |
| |
| return hwmon; |
| |
| err_free_temp_channel_desc: |
| kfree(hwmon->temp_channel_desc); |
| err_free_hwmon: |
| kfree(hwmon); |
| return ERR_PTR(err); |
| } |
| |
| static int mlx5_hwmon_dev_init(struct mlx5_hwmon *hwmon) |
| { |
| u32 mtcap_out[MLX5_ST_SZ_DW(mtcap_reg)] = {}; |
| u32 mtcap_in[MLX5_ST_SZ_DW(mtcap_reg)] = {}; |
| int err; |
| int i; |
| |
| err = mlx5_core_access_reg(hwmon->mdev, mtcap_in, sizeof(mtcap_in), |
| mtcap_out, sizeof(mtcap_out), |
| MLX5_REG_MTCAP, 0, 0); |
| if (err) |
| return err; |
| |
| mlx5_hwmon_channel_info_init(hwmon); |
| mlx5_hwmon_init_sensors_indexes(hwmon, MLX5_GET64(mtcap_reg, mtcap_out, sensor_map)); |
| err = mlx5_hwmon_init_channels_names(hwmon); |
| if (err) |
| return err; |
| |
| for (i = 0; i < hwmon->asic_platform_scount + hwmon->module_scount; i++) { |
| err = mlx5_hwmon_enable_max_temp(hwmon->mdev, |
| hwmon->temp_channel_desc[i].sensor_index); |
| if (err) |
| return err; |
| } |
| |
| hwmon->chip.ops = &mlx5_hwmon_ops; |
| hwmon->chip.info = (const struct hwmon_channel_info **)hwmon->channel_info; |
| |
| return 0; |
| } |
| |
| int mlx5_hwmon_dev_register(struct mlx5_core_dev *mdev) |
| { |
| struct device *dev = mdev->device; |
| struct mlx5_hwmon *hwmon; |
| int err; |
| |
| if (!MLX5_CAP_MCAM_REG(mdev, mtmp)) |
| return 0; |
| |
| hwmon = mlx5_hwmon_alloc(mdev); |
| if (IS_ERR(hwmon)) |
| return PTR_ERR(hwmon); |
| |
| err = mlx5_hwmon_dev_init(hwmon); |
| if (err) |
| goto err_free_hwmon; |
| |
| hwmon->hwmon_dev = hwmon_device_register_with_info(dev, "mlx5", |
| hwmon, |
| &hwmon->chip, |
| NULL); |
| if (IS_ERR(hwmon->hwmon_dev)) { |
| err = PTR_ERR(hwmon->hwmon_dev); |
| goto err_free_hwmon; |
| } |
| |
| mdev->hwmon = hwmon; |
| return 0; |
| |
| err_free_hwmon: |
| mlx5_hwmon_free(hwmon); |
| return err; |
| } |
| |
| void mlx5_hwmon_dev_unregister(struct mlx5_core_dev *mdev) |
| { |
| struct mlx5_hwmon *hwmon = mdev->hwmon; |
| |
| if (!hwmon) |
| return; |
| |
| hwmon_device_unregister(hwmon->hwmon_dev); |
| mlx5_hwmon_free(hwmon); |
| mdev->hwmon = NULL; |
| } |