summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/mellanox/mlx5/core/hwmon.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/mellanox/mlx5/core/hwmon.c')
-rw-r--r--drivers/net/ethernet/mellanox/mlx5/core/hwmon.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/hwmon.c b/drivers/net/ethernet/mellanox/mlx5/core/hwmon.c
new file mode 100644
index 000000000000..353f81dccd1c
--- /dev/null
+++ b/drivers/net/ethernet/mellanox/mlx5/core/hwmon.c
@@ -0,0 +1,418 @@
+// 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;
+}