root/drivers/vdpa/solidrun/snet_hwmon.c
// 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));
}