|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * SolidRun DPU driver for control plane | 
|  | * | 
|  | * Copyright (C) 2022-2023 SolidRun | 
|  | * | 
|  | * Author: Alvaro Karsz <alvaro.karsz@solid-run.com> | 
|  | * | 
|  | */ | 
|  | #include <linux/hwmon.h> | 
|  |  | 
|  | #include "snet_vdpa.h" | 
|  |  | 
|  | /* Monitor offsets */ | 
|  | #define SNET_MON_TMP0_IN_OFF      0x00 | 
|  | #define SNET_MON_TMP0_MAX_OFF     0x08 | 
|  | #define SNET_MON_TMP0_CRIT_OFF    0x10 | 
|  | #define SNET_MON_TMP1_IN_OFF      0x18 | 
|  | #define SNET_MON_TMP1_CRIT_OFF    0x20 | 
|  | #define SNET_MON_CURR_IN_OFF      0x28 | 
|  | #define SNET_MON_CURR_MAX_OFF     0x30 | 
|  | #define SNET_MON_CURR_CRIT_OFF    0x38 | 
|  | #define SNET_MON_PWR_IN_OFF       0x40 | 
|  | #define SNET_MON_VOLT_IN_OFF      0x48 | 
|  | #define SNET_MON_VOLT_CRIT_OFF    0x50 | 
|  | #define SNET_MON_VOLT_LCRIT_OFF   0x58 | 
|  |  | 
|  | static void snet_hwmon_read_reg(struct psnet *psnet, u32 reg, long *out) | 
|  | { | 
|  | *out = psnet_read64(psnet, psnet->cfg.hwmon_off + reg); | 
|  | } | 
|  |  | 
|  | static umode_t snet_howmon_is_visible(const void *data, | 
|  | enum hwmon_sensor_types type, | 
|  | u32 attr, int channel) | 
|  | { | 
|  | return 0444; | 
|  | } | 
|  |  | 
|  | static int snet_howmon_read(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, long *val) | 
|  | { | 
|  | struct psnet *psnet = dev_get_drvdata(dev); | 
|  | int ret = 0; | 
|  |  | 
|  | switch (type) { | 
|  | case hwmon_in: | 
|  | switch (attr) { | 
|  | case hwmon_in_lcrit: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_VOLT_LCRIT_OFF, val); | 
|  | break; | 
|  | case hwmon_in_crit: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_VOLT_CRIT_OFF, val); | 
|  | break; | 
|  | case hwmon_in_input: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_VOLT_IN_OFF, val); | 
|  | break; | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case hwmon_power: | 
|  | switch (attr) { | 
|  | case hwmon_power_input: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_PWR_IN_OFF, val); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case hwmon_curr: | 
|  | switch (attr) { | 
|  | case hwmon_curr_input: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_CURR_IN_OFF, val); | 
|  | break; | 
|  | case hwmon_curr_max: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_CURR_MAX_OFF, val); | 
|  | break; | 
|  | case hwmon_curr_crit: | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_CURR_CRIT_OFF, val); | 
|  | break; | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case hwmon_temp: | 
|  | switch (attr) { | 
|  | case hwmon_temp_input: | 
|  | if (channel == 0) | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_TMP0_IN_OFF, val); | 
|  | else | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_TMP1_IN_OFF, val); | 
|  | break; | 
|  | case hwmon_temp_max: | 
|  | if (channel == 0) | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_TMP0_MAX_OFF, val); | 
|  | else | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | case hwmon_temp_crit: | 
|  | if (channel == 0) | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_TMP0_CRIT_OFF, val); | 
|  | else | 
|  | snet_hwmon_read_reg(psnet, SNET_MON_TMP1_CRIT_OFF, val); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int snet_hwmon_read_string(struct device *dev, | 
|  | enum hwmon_sensor_types type, u32 attr, | 
|  | int channel, const char **str) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | switch (type) { | 
|  | case hwmon_in: | 
|  | *str = "main_vin"; | 
|  | break; | 
|  | case hwmon_power: | 
|  | *str = "soc_pin"; | 
|  | break; | 
|  | case hwmon_curr: | 
|  | *str = "soc_iin"; | 
|  | break; | 
|  | case hwmon_temp: | 
|  | if (channel == 0) | 
|  | *str = "power_stage_temp"; | 
|  | else | 
|  | *str = "ic_junction_temp"; | 
|  | break; | 
|  | default: | 
|  | ret = -EOPNOTSUPP; | 
|  | break; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct hwmon_ops snet_hwmon_ops = { | 
|  | .is_visible = snet_howmon_is_visible, | 
|  | .read = snet_howmon_read, | 
|  | .read_string = snet_hwmon_read_string | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_channel_info * const snet_hwmon_info[] = { | 
|  | HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL, | 
|  | HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL), | 
|  | HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL), | 
|  | HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_CRIT | HWMON_C_LABEL), | 
|  | HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_LCRIT | HWMON_I_LABEL), | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_chip_info snet_hwmono_info = { | 
|  | .ops = &snet_hwmon_ops, | 
|  | .info = snet_hwmon_info, | 
|  | }; | 
|  |  | 
|  | /* Create an HW monitor device */ | 
|  | void psnet_create_hwmon(struct pci_dev *pdev) | 
|  | { | 
|  | struct device *hwmon; | 
|  | struct psnet *psnet = pci_get_drvdata(pdev); | 
|  |  | 
|  | snprintf(psnet->hwmon_name, SNET_NAME_SIZE, "snet_%s", pci_name(pdev)); | 
|  | hwmon = devm_hwmon_device_register_with_info(&pdev->dev, psnet->hwmon_name, psnet, | 
|  | &snet_hwmono_info, NULL); | 
|  | /* The monitor is not mandatory, Just alert user in case of an error */ | 
|  | if (IS_ERR(hwmon)) | 
|  | SNET_WARN(pdev, "Failed to create SNET hwmon, error %ld\n", PTR_ERR(hwmon)); | 
|  | } |